diff --git a/docker-compose.mongo-only.yml b/docker-compose.mongo-only.yml index 9678939a3f..3abb4947c6 100644 --- a/docker-compose.mongo-only.yml +++ b/docker-compose.mongo-only.yml @@ -21,3 +21,4 @@ services: timeout: 30s start_period: 0s retries: 30 + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a839cca9c9..b07b0ba8da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "micromustache": "^8.0.3", "moment": "^2.29.4", "moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119", - "mongoose": "^8.9.5", + "mongoose": "^8.23.0", "morgan": "^1.10.1", "nan": "^2.25.0", "nconf": "^0.12.1", @@ -2623,6 +2623,19 @@ "node": ">=12.0.0" } }, + "node_modules/@google-cloud/trace-agent/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@google-cloud/trace-agent/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3051,9 +3064,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", - "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -6299,6 +6312,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", "dev": true, + "license": "MIT", "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -6308,13 +6322,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/bl/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6330,6 +6346,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -11207,18 +11224,6 @@ "node": ">=12" } }, - "node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11962,6 +11967,19 @@ "node": ">=12" } }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/google-auth-library/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -15522,143 +15540,14 @@ } }, "node_modules/mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", - "dev": true, - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", - "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", - "license": "MIT", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongodb/node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/mongoose": { - "version": "8.9.7", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz", - "integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==", - "license": "MIT", - "dependencies": { - "bson": "^6.10.1", - "kareem": "2.6.3", - "mongodb": "~6.12.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/node_modules/kerberos": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.2.2.tgz", - "integrity": "sha512-42O7+/1Zatsc3MkxaMPpXcIl/ukIrbQaGoArZEAr6GcEi2qhfprOBYOPhj+YvSMJkEkdpTjApUx+2DuWaKwRhg==", - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.2" - }, - "engines": { - "node": ">=12.9.0" - } - }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", - "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" @@ -15669,7 +15558,7 @@ "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -15696,104 +15585,77 @@ } } }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz", + "integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/mongoose/node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "optional": true, - "peer": true - }, - "node_modules/mongoose/node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", - "optional": true, - "peer": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongoose/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "optional": true, - "peer": true - }, - "node_modules/mongoose/node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongoose/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongoose/node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/monk": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/monk/-/monk-7.3.4.tgz", @@ -15848,6 +15710,56 @@ "integrity": "sha512-jSTz73B/+pGTTvhu5Ym8xsG6+QqaWab53UXnXdNNlTijTdLvcHABCLJXudQiJxob5N1Mzr5EOSx5ziwn2sihPQ==", "dev": true }, + "node_modules/monk/node_modules/bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/monk/node_modules/mongodb": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", + "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, "node_modules/morgan": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", @@ -17194,10 +17106,11 @@ } }, "node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.10.tgz", + "integrity": "sha512-0r3OB9EIQsP+a5HVATHq2ExIy2q/Vaffoo4IAikW1spCYswhLxqWQS0i3GwS3AdY/OIP4SWZHLGz8CMU558PGw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "require-at": "^1.0.6" }, @@ -18679,6 +18592,7 @@ "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=4" } @@ -18977,6 +18891,7 @@ "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "sparse-bitfield": "^3.0.3" diff --git a/package.json b/package.json index bb168502bf..254b0741fd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "micromustache": "^8.0.3", "moment": "^2.29.4", "moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119", - "mongoose": "^8.9.5", + "mongoose": "^8.23.0", "morgan": "^1.10.1", "nan": "^2.25.0", "nconf": "^0.12.1", diff --git a/test/api/unit/libs/analyticsService.test.js b/test/api/unit/libs/analyticsService.test.js deleted file mode 100644 index b07f08df9b..0000000000 --- a/test/api/unit/libs/analyticsService.test.js +++ /dev/null @@ -1,560 +0,0 @@ -/* eslint-disable camelcase */ -import nconf from 'nconf'; -import Amplitude from 'amplitude'; -import * as analyticsService from '../../../../website/server/libs/analyticsService'; - -describe('analyticsService', () => { - beforeEach(() => { - sandbox.stub(Amplitude.prototype, 'track').returns(Promise.resolve()); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('#getServiceByEnvironment', () => { - it('returns mock methods when not in production', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); - expect(analyticsService.getAnalyticsServiceByEnvironment()) - .to.equal(analyticsService.mockAnalyticsService); - }); - - it('returns real methods when in production', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - expect(analyticsService.getAnalyticsServiceByEnvironment().track) - .to.equal(analyticsService.track); - expect(analyticsService.getAnalyticsServiceByEnvironment().trackPurchase) - .to.equal(analyticsService.trackPurchase); - }); - }); - - describe('#track', () => { - let eventType; let - data; - - beforeEach(() => { - eventType = 'Cron'; - data = { - category: 'behavior', - uuid: 'unique-user-id', - resting: true, - cronCount: 5, - headers: { - 'x-client': 'habitica-web', - 'user-agent': '', - }, - user: { - preferences: { - analyticsConsent: true, - }, - }, - }; - }); - - context('Amplitude', () => { - it('calls out to amplitude', () => analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledOnce; - })); - - it('uses a dummy user id if none is provided', () => { - delete data.uuid; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - user_id: 'no-user-id-was-provided', - }); - }); - }); - - context('platform', () => { - it('logs web platform', () => { - data.headers = { 'x-client': 'habitica-web' }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Web', - }); - }); - }); - - it('logs iOS platform', () => { - data.headers = { 'x-client': 'habitica-ios' }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'iOS', - }); - }); - }); - - it('logs Android platform', () => { - data.headers = { 'x-client': 'habitica-android' }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Android', - }); - }); - }); - - it('logs 3rd Party platform', () => { - data.headers = { 'x-client': 'some-third-party' }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: '3rd Party', - }); - }); - }); - - it('logs unknown if headers are not passed in', () => { - delete data.headers; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Unknown', - }); - }); - }); - }); - - context('Operating System', () => { - it('sets default', () => { - data.headers = { - 'x-client': 'third-party', - 'user-agent': 'foo', - }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'Other', - os_version: '0', - }); - }); - }); - - it('sets iOS', () => { - data.headers = { - 'x-client': 'habitica-ios', - 'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)', - }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'iOS', - os_version: '9.3.0', - }); - }); - }); - - it('sets Android', () => { - data.headers = { - 'x-client': 'habitica-android', - 'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19', - }; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'Android', - os_version: '4.0.4', - }); - }); - }); - - it('sets Unknown if headers are not passed in', () => { - delete data.headers; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: undefined, - os_version: undefined, - }); - }); - }); - }); - - it('sends details about event', () => analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - category: 'behavior', - resting: true, - cronCount: 5, - }, - }); - })); - - it('sends english item name for gear if itemKey is provided', () => { - data.itemKey = 'headAccessory_special_foxEars'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Fox Ears', - }, - }); - }); - }); - - it('sends english item name for egg if itemKey is provided', () => { - data.itemKey = 'Wolf'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Wolf Egg', - }, - }); - }); - }); - - it('sends english item name for food if itemKey is provided', () => { - data.itemKey = 'Cake_Skeleton'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Bare Bones Cake', - }, - }); - }); - }); - - it('sends english item name for hatching potion if itemKey is provided', () => { - data.itemKey = 'Golden'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Golden Hatching Potion', - }, - }); - }); - }); - - it('sends english item name for quest if itemKey is provided', () => { - data.itemKey = 'atom1'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Attack of the Mundane, Part 1: Dish Disaster!', - }, - }); - }); - }); - - it('sends english item name for purchased spell if itemKey is provided', () => { - data.itemKey = 'seafoam'; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - itemKey: data.itemKey, - itemName: 'Seafoam', - }, - }); - }); - }); - - it('sends user data if provided', () => { - const stats = { - class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30, - }; - const user = { - stats, - contributor: { level: 1 }, - purchased: { plan: { planId: 'foo-plan' } }, - flags: { tour: { intro: -2 } }, - habits: [{ _id: 'habit' }], - dailys: [{ _id: 'daily' }], - todos: [{ _id: 'todo' }], - rewards: [{ _id: 'reward' }], - balance: 12, - loginIncentives: 1, - preferences: { - analyticsConsent: true, - }, - }; - - data.user = user; - - return analyticsService.track(eventType, data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - user_properties: { - Class: 'wizard', - Experience: 5, - Gold: 23, - Health: 10, - Level: 4, - Mana: 30, - tutorialComplete: true, - 'Number Of Tasks': { - habits: 1, - dailys: 1, - todos: 1, - rewards: 1, - }, - contributorLevel: 1, - subscription: 'foo-plan', - balance: 12, - balanceGemAmount: 48, - loginIncentives: 1, - }, - }); - }); - }); - }); - }); - - describe('#trackPurchase', () => { - let data; - - beforeEach(() => { - data = { - uuid: 'user-id', - sku: 'paypal-checkout', - paymentMethod: 'PayPal', - itemPurchased: 'Gems', - purchaseValue: 8, - purchaseType: 'checkout', - gift: false, - quantity: 1, - headers: { - 'x-client': 'habitica-web', - 'user-agent': '', - }, - user: { - preferences: { - analyticsConsent: true, - }, - }, - }; - }); - - context('Amplitude', () => { - it('calls out to amplitude', () => analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledOnce; - })); - - it('uses a dummy user id if none is provided', () => { - delete data.uuid; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - user_id: 'no-user-id-was-provided', - }); - }); - }); - - context('platform', () => { - it('logs web platform', () => { - data.headers = { 'x-client': 'habitica-web' }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Web', - }); - }); - }); - - it('logs iOS platform', () => { - data.headers = { 'x-client': 'habitica-ios' }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'iOS', - }); - }); - }); - - it('logs Android platform', () => { - data.headers = { 'x-client': 'habitica-android' }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Android', - }); - }); - }); - - it('logs 3rd Party platform', () => { - data.headers = { 'x-client': 'some-third-party' }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: '3rd Party', - }); - }); - }); - - it('logs unknown if headers are not passed in', () => { - delete data.headers; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - platform: 'Unknown', - }); - }); - }); - }); - - context('Operating System', () => { - it('sets default', () => { - data.headers = { - 'x-client': 'third-party', - 'user-agent': 'foo', - }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'Other', - os_version: '0', - }); - }); - }); - - it('sets iOS', () => { - data.headers = { - 'x-client': 'habitica-ios', - 'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)', - }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'iOS', - os_version: '9.3.0', - }); - }); - }); - - it('sets Android', () => { - data.headers = { - 'x-client': 'habitica-android', - 'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19', - }; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: 'Android', - os_version: '4.0.4', - }); - }); - }); - - it('sets Unknown if headers are not passed in', () => { - delete data.headers; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - os_name: undefined, - os_version: undefined, - }); - }); - }); - }); - - it('sends details about purchase', () => analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - event_properties: { - gift: false, - itemPurchased: 'Gems', - paymentMethod: 'PayPal', - purchaseType: 'checkout', - quantity: 1, - sku: 'paypal-checkout', - }, - }); - })); - - it('sends user data if provided', () => { - const stats = { - class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30, - }; - const user = { - stats, - contributor: { level: 1 }, - purchased: { plan: { planId: 'foo-plan' } }, - flags: { tour: { intro: -2 } }, - habits: [{ _id: 'habit' }], - dailys: [{ _id: 'daily' }], - todos: [{ _id: 'todo' }], - rewards: [{ _id: 'reward' }], - preferences: { - analyticsConsent: true, - }, - }; - - data.user = user; - - return analyticsService.trackPurchase(data) - .then(() => { - expect(Amplitude.prototype.track).to.be.calledWithMatch({ - user_properties: { - Class: 'wizard', - Experience: 5, - Gold: 23, - Health: 10, - Level: 4, - Mana: 30, - tutorialComplete: true, - 'Number Of Tasks': { - habits: 1, - dailys: 1, - todos: 1, - rewards: 1, - }, - contributorLevel: 1, - subscription: 'foo-plan', - }, - }); - }); - }); - }); - }); - - describe('mockAnalyticsService', () => { - it('has stubbed track method', () => { - expect(analyticsService.mockAnalyticsService).to.respondTo('track'); - }); - - it('has stubbed trackPurchase method', () => { - expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase'); - }); - }); -}); diff --git a/test/api/unit/libs/cron.test.js b/test/api/unit/libs/cron.test.js index 31d3b6488c..cf51ac5648 100644 --- a/test/api/unit/libs/cron.test.js +++ b/test/api/unit/libs/cron.test.js @@ -13,7 +13,6 @@ import { cron, cronWrapper } from '../../../../website/server/libs/cron'; import { model as User } from '../../../../website/server/models/user'; import * as Tasks from '../../../../website/server/models/task'; import common from '../../../../website/common'; -import * as analytics from '../../../../website/server/libs/analyticsService'; import { model as Group } from '../../../../website/server/models/group'; const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime(); @@ -41,20 +40,17 @@ describe('cron', async () => { }, }, }); - - sinon.spy(analytics, 'track'); }); afterEach(async () => { if (clock !== null) clock.restore(); - analytics.track.restore(); }); it('updates user.preferences.timezoneOffsetAtLastCron', async () => { const timezoneUtcOffsetFromUserPrefs = -1; await cron({ - user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs, + user, tasksByType, daysMissed, timezoneUtcOffsetFromUserPrefs, }); expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1); @@ -63,7 +59,7 @@ describe('cron', async () => { it('resets user.items.lastDrop.count', async () => { user.items.lastDrop.count = 4; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.items.lastDrop.count).to.equal(0); }); @@ -71,26 +67,11 @@ describe('cron', async () => { it('increments user cron count', async () => { const cronCountBefore = user.flags.cronCount; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore); }); - it('calls analytics', async () => { - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(analytics.track.callCount).to.equal(1); - }); - - it('calls analytics when user is sleeping', async () => { - user.preferences.sleep = true; - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(analytics.track.callCount).to.equal(1); - }); - describe('end of the month perks', async () => { beforeEach(async () => { user.purchased.plan.customerId = 'subscribedId'; @@ -101,7 +82,7 @@ describe('cron', async () => { user.purchased.plan.dateUpdated = new Date('2018-12-11'); clock = sinon.useFakeTimers(new Date('2019-01-29')); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.mysteryItems.length).to.eql(2); const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS'); @@ -112,7 +93,7 @@ describe('cron', async () => { user.purchased.plan.dateUpdated = new Date('2018-11-11'); clock = sinon.useFakeTimers(new Date('2019-01-29')); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.mysteryItems.length).to.eql(4); const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS'); @@ -122,7 +103,7 @@ describe('cron', async () => { it('resets plan.gemsBought on a new month', async () => { user.purchased.plan.gemsBought = 10; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.gemsBought).to.equal(0); }); @@ -131,7 +112,7 @@ describe('cron', async () => { user.purchased.plan.gemsBought = 10; user.purchased.plan.dateUpdated = undefined; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.gemsBought).to.equal(0); }); @@ -142,7 +123,7 @@ describe('cron', async () => { user.purchased.plan.gemsBought = 10; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.gemsBought).to.equal(10); }); @@ -150,7 +131,7 @@ describe('cron', async () => { it('resets plan.dateUpdated on a new month', async () => { const currentMonth = moment().startOf('month'); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(moment(user.purchased.plan.dateUpdated).startOf('month').isSame(currentMonth)).to.eql(true); }); @@ -158,7 +139,7 @@ describe('cron', async () => { it('increments plan.consecutive.count', async () => { user.purchased.plan.consecutive.count = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.count).to.equal(1); }); @@ -166,7 +147,7 @@ describe('cron', async () => { it('increments plan.cumulativeCount', async () => { user.purchased.plan.cumulativeCount = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.cumulativeCount).to.equal(1); }); @@ -175,7 +156,7 @@ describe('cron', async () => { user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate(); user.purchased.plan.consecutive.count = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.count).to.equal(2); }); @@ -184,7 +165,7 @@ describe('cron', async () => { user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate(); user.purchased.plan.cumulativeCount = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.cumulativeCount).to.equal(3); }); @@ -196,7 +177,7 @@ describe('cron', async () => { user.purchased.plan.consecutive.trinkets = 1; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.trinkets).to.equal(1); @@ -206,7 +187,7 @@ describe('cron', async () => { user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); @@ -214,7 +195,7 @@ describe('cron', async () => { it('does not reset plan stats if we are before the last day of the cancelled month', async () => { user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.customerId).to.exist; }); @@ -225,7 +206,7 @@ describe('cron', async () => { user.purchased.plan.consecutive.count = 5; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.customerId).to.not.exist; @@ -264,7 +245,7 @@ describe('cron', async () => { // Add 2 days so that we're sure we're not affected by any start-of-month effects // e.g., from time zone oddness. await cron({ - user: user1, tasksByType, daysMissed, analytics, + user: user1, tasksByType, daysMissed, }); expect(user1.purchased.plan.consecutive.count).to.equal(1); expect(user1.purchased.plan.consecutive.trinkets).to.equal(2); @@ -276,7 +257,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user1, tasksByType, daysMissed, analytics, + user: user1, tasksByType, daysMissed, }); expect(user1.purchased.plan.consecutive.count).to.equal(10); expect(user1.purchased.plan.consecutive.trinkets).to.equal(11); @@ -311,7 +292,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user3, tasksByType, daysMissed, analytics, + user: user3, tasksByType, daysMissed, }); expect(user3.purchased.plan.consecutive.count).to.equal(1); expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); @@ -323,7 +304,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user3, tasksByType, daysMissed, analytics, + user: user3, tasksByType, daysMissed, }); expect(user3.purchased.plan.consecutive.count).to.equal(10); expect(user3.purchased.plan.consecutive.trinkets).to.equal(11); @@ -358,7 +339,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user6, tasksByType, daysMissed, analytics, + user: user6, tasksByType, daysMissed, }); expect(user6.purchased.plan.consecutive.count).to.equal(1); expect(user6.purchased.plan.consecutive.trinkets).to.equal(2); @@ -391,7 +372,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user12, tasksByType, daysMissed, analytics, + user: user12, tasksByType, daysMissed, }); expect(user12.purchased.plan.consecutive.count).to.equal(1); expect(user12.purchased.plan.consecutive.trinkets).to.equal(2); @@ -403,7 +384,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user12, tasksByType, daysMissed, analytics, + user: user12, tasksByType, daysMissed, }); expect(user12.purchased.plan.consecutive.count).to.equal(10); expect(user12.purchased.plan.consecutive.trinkets).to.equal(11); @@ -439,7 +420,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user3g, tasksByType, daysMissed, analytics, + user: user3g, tasksByType, daysMissed, }); expect(user3g.purchased.plan.consecutive.count).to.equal(1); expect(user3g.purchased.plan.cumulativeCount).to.equal(1); @@ -452,7 +433,7 @@ describe('cron', async () => { .add(2, 'days') .toDate()); await cron({ - user: user3g, tasksByType, daysMissed, analytics, + user: user3g, tasksByType, daysMissed, }); // subscription has been erased by now expect(user3g.purchased.plan.consecutive.count).to.equal(0); @@ -471,7 +452,7 @@ describe('cron', async () => { it('resets plan.gemsBought on a new month', async () => { user.purchased.plan.gemsBought = 10; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.gemsBought).to.equal(0); }); @@ -482,14 +463,14 @@ describe('cron', async () => { user.purchased.plan.gemsBought = 10; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.gemsBought).to.equal(10); }); it('does not reset plan.dateUpdated on a new month', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.dateUpdated).to.be.empty; }); @@ -497,7 +478,7 @@ describe('cron', async () => { it('does not increment plan.consecutive.count', async () => { user.purchased.plan.consecutive.count = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.count).to.equal(0); }); @@ -505,7 +486,7 @@ describe('cron', async () => { it('does not increment plan.cumulativeCount', async () => { user.purchased.plan.cumulativeCount = 0; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.cumulativeCount).to.equal(0); }); @@ -513,7 +494,7 @@ describe('cron', async () => { it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => { user.purchased.plan.consecutive.count = 5; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.trinkets).to.equal(0); }); @@ -521,7 +502,7 @@ describe('cron', async () => { it('does not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', async () => { user.purchased.plan.consecutive.count = 5; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0); }); @@ -530,7 +511,7 @@ describe('cron', async () => { user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); @@ -538,7 +519,7 @@ describe('cron', async () => { it('does nothing to plan stats if we are before the last day of the cancelled month', async () => { user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.purchased.plan.customerId).to.not.exist; }); @@ -564,7 +545,7 @@ describe('cron', async () => { it('should make uncompleted todos redder', async () => { const valueBefore = tasksByType.todos[0].value; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore); }); @@ -573,7 +554,7 @@ describe('cron', async () => { tasksByType.todos[0].completed = true; const valueBefore = tasksByType.todos[0].value; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.todos[0].value).to.equal(valueBefore); }); @@ -582,7 +563,7 @@ describe('cron', async () => { tasksByType.todos[0].completed = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.history.todos).to.be.lengthOf(1); @@ -608,7 +589,7 @@ describe('cron', async () => { expect(user.tasksOrder.todos).to.be.lengthOf(3); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); // user.tasksOrder.todos should be filtered while tasks by type remains unchanged @@ -635,7 +616,7 @@ describe('cron', async () => { const original = user.tasksOrder.todos; // Preserve the original order await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); let listsAreEqual = true; @@ -675,7 +656,7 @@ describe('cron', async () => { tasksByType.dailys[0].everyX = 5; tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate(); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].isDue).to.be.false; }); @@ -686,7 +667,7 @@ describe('cron', async () => { tasksByType.dailys[0].everyX = 5; tasksByType.dailys[0].startDate = moment().toDate(); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].isDue).to.exist; }); @@ -696,14 +677,14 @@ describe('cron', async () => { tasksByType.dailys[0].everyX = 5; tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate(); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].nextDue.length).to.eql(6); }); it('should add history', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].history).to.be.lengthOf(1); }); @@ -711,7 +692,7 @@ describe('cron', async () => { it('should set tasks completed to false', async () => { tasksByType.dailys[0].completed = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].completed).to.be.false; }); @@ -720,7 +701,7 @@ describe('cron', async () => { user.preferences.sleep = true; tasksByType.dailys[0].completed = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].completed).to.be.false; }); @@ -729,7 +710,7 @@ describe('cron', async () => { tasksByType.dailys[0].checklist.push({ title: 'test', completed: false }); tasksByType.dailys[0].completed = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; }); @@ -739,7 +720,7 @@ describe('cron', async () => { tasksByType.dailys[0].checklist.push({ title: 'test', completed: false }); tasksByType.dailys[0].completed = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; }); @@ -749,7 +730,7 @@ describe('cron', async () => { tasksByType.dailys[0].checklist.push({ title: 'test', completed: false }); tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; }); @@ -759,7 +740,7 @@ describe('cron', async () => { const hpBefore = user.stats.hp; tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.hp).to.be.lessThan(hpBefore); }); @@ -770,7 +751,7 @@ describe('cron', async () => { const hpBefore = user.stats.hp; tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.hp).to.equal(hpBefore); }); @@ -784,7 +765,7 @@ describe('cron', async () => { tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); cronOverride({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.hp).to.equal(hpBefore); @@ -797,7 +778,7 @@ describe('cron', async () => { tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.hp).to.equal(hpBefore); @@ -808,7 +789,7 @@ describe('cron', async () => { let hpBefore = user.stats.hp; tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); const hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp; @@ -816,7 +797,7 @@ describe('cron', async () => { tasksByType.dailys[0].checklist.push({ title: 'test', completed: true }); tasksByType.dailys[0].checklist.push({ title: 'test2', completed: false }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); const hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp; @@ -829,7 +810,7 @@ describe('cron', async () => { tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); const progress = await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(progress.down).to.equal(-1); @@ -841,7 +822,7 @@ describe('cron', async () => { tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 }); const progress = await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(progress.down).to.equal(0); @@ -862,7 +843,7 @@ describe('cron', async () => { tasksByType.dailys[1].frequency = 'daily'; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.hp).to.equal(48); @@ -886,7 +867,7 @@ describe('cron', async () => { tasksByType.habits[0].down = false; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].value).to.be.lessThan(1); @@ -897,7 +878,7 @@ describe('cron', async () => { tasksByType.habits[0].up = false; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].value).to.be.lessThan(1); @@ -909,7 +890,7 @@ describe('cron', async () => { tasksByType.habits[0].down = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].value).to.equal(1); @@ -928,7 +909,7 @@ describe('cron', async () => { tasksByType.habits[0].counterDown = 1; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -941,7 +922,7 @@ describe('cron', async () => { tasksByType.habits[0].counterDown = 1; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -955,7 +936,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -964,7 +945,7 @@ describe('cron', async () => { // should reset daysMissed = 8; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -988,7 +969,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -1002,7 +983,7 @@ describe('cron', async () => { // should reset after user CDS await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1026,7 +1007,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -1036,7 +1017,7 @@ describe('cron', async () => { // should reset daysMissed = 2; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1060,7 +1041,7 @@ describe('cron', async () => { // should reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1084,7 +1065,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -1098,7 +1079,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -1107,7 +1088,7 @@ describe('cron', async () => { // should reset daysMissed = 32; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1132,7 +1113,7 @@ describe('cron', async () => { // should reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1156,7 +1137,7 @@ describe('cron', async () => { // should not reset await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(1); @@ -1166,7 +1147,7 @@ describe('cron', async () => { // should reset daysMissed = 2; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(tasksByType.habits[0].counterUp).to.equal(0); @@ -1199,7 +1180,7 @@ describe('cron', async () => { user.stats.lvl = 2; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.history.exp).to.have.lengthOf(1); @@ -1212,7 +1193,7 @@ describe('cron', async () => { tasksByType.dailys[0].isDue = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.achievements.perfect).to.equal(1); @@ -1224,7 +1205,7 @@ describe('cron', async () => { tasksByType.dailys[0].isDue = false; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.achievements.perfect).to.equal(0); @@ -1238,7 +1219,7 @@ describe('cron', async () => { const previousBuffs = user.stats.buffs.toObject(); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); @@ -1256,7 +1237,7 @@ describe('cron', async () => { const previousBuffs = user.stats.buffs.toObject(); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); @@ -1280,7 +1261,7 @@ describe('cron', async () => { }; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.equal(0); @@ -1307,7 +1288,7 @@ describe('cron', async () => { }; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.equal(0); @@ -1333,7 +1314,7 @@ describe('cron', async () => { }; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.equal(0); @@ -1360,7 +1341,7 @@ describe('cron', async () => { }; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.equal(0); @@ -1381,7 +1362,7 @@ describe('cron', async () => { const previousBuffs = user.stats.buffs.toObject(); cronOverride({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); @@ -1401,7 +1382,7 @@ describe('cron', async () => { const previousBuffs = user.stats.buffs.toObject(); cronOverride({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); @@ -1420,7 +1401,7 @@ describe('cron', async () => { tasksByType.dailys[0].completed = true; stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 })); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.mp).to.be.greaterThan(mpBefore); @@ -1436,7 +1417,7 @@ describe('cron', async () => { tasksByType.dailys[0].completed = true; stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 })); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.mp).to.equal(mpBefore); @@ -1449,7 +1430,7 @@ describe('cron', async () => { user.stats.mp = 120; stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 })); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.stats.mp).to.equal(common.statsComputed(user).maxMP); @@ -1482,7 +1463,7 @@ describe('cron', async () => { it('resets user progress', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.party.quest.progress.up).to.equal(0); expect(user.party.quest.progress.down).to.equal(0); @@ -1491,7 +1472,7 @@ describe('cron', async () => { it('applies the user progress', async () => { const progress = await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(progress.down).to.equal(-1); }); @@ -1529,19 +1510,19 @@ describe('cron', async () => { describe('login incentives', async () => { it('increments incentive counter each cron', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(1); user.lastCron = moment(new Date()).subtract({ days: 1 }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(2); }); it('pushes a notification of the day\'s incentive each cron', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.notifications.length).to.eql(1); expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE'); @@ -1549,13 +1530,13 @@ describe('cron', async () => { it('replaces previous notifications', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); const filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE'); @@ -1566,7 +1547,7 @@ describe('cron', async () => { it('increments loginIncentives by 1 even if days are skipped in between', async () => { daysMissed = 3; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(1); }); @@ -1574,14 +1555,14 @@ describe('cron', async () => { it('increments loginIncentives by 1 even if user is sleeping', async () => { user.preferences.sleep = true; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(1); }); it('awards user bard robes if login incentive is 1', async () => { await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(1); expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true); @@ -1591,7 +1572,7 @@ describe('cron', async () => { it('awards user incentive backgrounds if login incentive is 2', async () => { user.loginIncentives = 1; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(2); expect(user.purchased.background.blue).to.eql(true); @@ -1605,7 +1586,7 @@ describe('cron', async () => { it('awards user Bard Hat if login incentive is 3', async () => { user.loginIncentives = 2; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(3); expect(user.items.gear.owned.head_special_bardHat).to.eql(true); @@ -1615,7 +1596,7 @@ describe('cron', async () => { it('awards user RoyalPurple Hatching Potion if login incentive is 4', async () => { user.loginIncentives = 3; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(4); expect(user.items.hatchingPotions.RoyalPurple).to.eql(1); @@ -1625,7 +1606,7 @@ describe('cron', async () => { it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', async () => { user.loginIncentives = 4; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(5); @@ -1639,7 +1620,7 @@ describe('cron', async () => { it('awards user moon quest if login incentive is 7', async () => { user.loginIncentives = 6; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(7); expect(user.items.quests.moon1).to.eql(1); @@ -1649,7 +1630,7 @@ describe('cron', async () => { it('awards user RoyalPurple Hatching Potion if login incentive is 10', async () => { user.loginIncentives = 9; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(10); expect(user.items.hatchingPotions.RoyalPurple).to.eql(1); @@ -1659,7 +1640,7 @@ describe('cron', async () => { it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', async () => { user.loginIncentives = 13; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(14); @@ -1673,7 +1654,7 @@ describe('cron', async () => { it('awards user a bard instrument if login incentive is 18', async () => { user.loginIncentives = 17; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(18); expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true); @@ -1683,7 +1664,7 @@ describe('cron', async () => { it('awards user second moon quest if login incentive is 22', async () => { user.loginIncentives = 21; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(22); expect(user.items.quests.moon2).to.eql(1); @@ -1693,7 +1674,7 @@ describe('cron', async () => { it('awards user a RoyalPurple hatching potion if login incentive is 26', async () => { user.loginIncentives = 25; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(26); expect(user.items.hatchingPotions.RoyalPurple).to.eql(1); @@ -1703,7 +1684,7 @@ describe('cron', async () => { it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', async () => { user.loginIncentives = 29; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(30); @@ -1718,7 +1699,7 @@ describe('cron', async () => { it('awards user a RoyalPurple hatching potion if login incentive is 35', async () => { user.loginIncentives = 34; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(35); expect(user.items.hatchingPotions.RoyalPurple).to.eql(1); @@ -1728,7 +1709,7 @@ describe('cron', async () => { it('awards user the third moon quest if login incentive is 40', async () => { user.loginIncentives = 39; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(40); expect(user.items.quests.moon3).to.eql(1); @@ -1738,7 +1719,7 @@ describe('cron', async () => { it('awards user a RoyalPurple hatching potion if login incentive is 45', async () => { user.loginIncentives = 44; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(45); expect(user.items.hatchingPotions.RoyalPurple).to.eql(1); @@ -1748,7 +1729,7 @@ describe('cron', async () => { it('awards user a saddle if login incentive is 50', async () => { user.loginIncentives = 49; await cron({ - user, tasksByType, daysMissed, analytics, + user, tasksByType, daysMissed, }); expect(user.loginIncentives).to.eql(50); expect(user.items.food.Saddle).to.eql(1); @@ -1766,7 +1747,6 @@ describe('cron wrapper', () => { res = generateRes(); req = generateReq(); user = await res.locals.user.save(); - res.analytics = analytics; }); afterEach(() => { diff --git a/test/api/unit/libs/localAnalytics.test.js b/test/api/unit/libs/localAnalytics.test.js new file mode 100644 index 0000000000..e255fea262 --- /dev/null +++ b/test/api/unit/libs/localAnalytics.test.js @@ -0,0 +1,100 @@ +import nconf from 'nconf'; +import requireAgain from 'require-again'; +import { model as User } from '../../../../website/server/models/user'; +import { RegistrationEventModel } from '../../../../website/server/models/analytics/registrationEvent'; +import { SubscriptionEventModel } from '../../../../website/server/models/analytics/subscriptionEvent'; + +describe('localAnalytics', () => { + let user; + let localAnalytics; + before(() => { + const nconfGetStub = sandbox.stub(nconf, 'get'); + nconfGetStub.withArgs('ANALYTICS_DB').returns('analytics'); + nconfGetStub.withArgs('DISABLE_LOCAL_ANALYTICS').returns(false); + localAnalytics = requireAgain('../../../../website/server/libs/localAnalytics'); + }); + + beforeEach(async () => { + user = new User({ + auth: { + local: { + username: 'username', + email: 'email@example.com', + }, + }, + registeredThrough: 'habitica-web', + }); + }); + + describe('trackRegistrationEvent', () => { + afterEach(async () => { + await RegistrationEventModel.deleteMany({}); + }); + + it('creates a registration event when a user registers', async () => { + user._id = '00000000-0000-0000-0000-000000000001'; + await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.1' }); + + const registrationEvents = await RegistrationEventModel.find({ userId: user._id }); + expect(registrationEvents).to.have.lengthOf(1); + expect(registrationEvents[0]).to.have.property('userId', user._id); + expect(registrationEvents[0]).to.have.property('ipAddress', '127.0.0.1'); + }); + + it('saves the correct data to the database', async () => { + user._id = '00000000-0000-0000-0000-000000000002'; + user.auth.google = { id: 'abc', emails: [{ value: 'email@example.com' }] }; + await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.2' }); + + const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id }); + expect(registrationEvent).to.have.property('userId', user._id); + expect(registrationEvent).to.have.property('ipAddress', '127.0.0.2'); + expect(registrationEvent).to.have.property('authenticationMethod', 'google'); + }); + }); + + describe('trackSubscriptionEvent', () => { + afterEach(async () => { + await SubscriptionEventModel.deleteMany({}); + }); + + it('creates a subscription event when a user subscribes', async () => { + user._id = '00000000-0000-0000-0000-000000000003'; + await localAnalytics.trackSubscriptionEvent({ + eventType: 'subscribed', + user, + paymentMethod: 'stripe', + customerId: 'cus_123', + planId: 'plan_123', + }); + + const subscriptionEvents = await SubscriptionEventModel.find({ userId: user._id }); + expect(subscriptionEvents).to.have.lengthOf(1); + expect(subscriptionEvents[0]).to.have.property('userId', user._id); + expect(subscriptionEvents[0]).to.have.property('eventType', 'subscribed'); + expect(subscriptionEvents[0]).to.have.property('paymentMethod', 'stripe'); + expect(subscriptionEvents[0]).to.have.property('customerId', 'cus_123'); + expect(subscriptionEvents[0]).to.have.property('planId', 'plan_123'); + }); + + it('creates a subscription event with cancellation reason when a user cancels', async () => { + user._id = '00000000-0000-0000-0000-000000000004'; + await localAnalytics.trackSubscriptionEvent({ + eventType: 'cancelled', + user, + paymentMethod: 'stripe', + customerId: 'cus_456', + planId: 'plan_456', + cancellationReason: 'No longer needed', + }); + + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id }); + expect(subscriptionEvent).to.have.property('userId', user._id); + expect(subscriptionEvent).to.have.property('eventType', 'cancelled'); + expect(subscriptionEvent).to.have.property('paymentMethod', 'stripe'); + expect(subscriptionEvent).to.have.property('customerId', 'cus_456'); + expect(subscriptionEvent).to.have.property('planId', 'plan_456'); + expect(subscriptionEvent).to.have.property('cancellationReason', 'No longer needed'); + }); + }); +}); diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 00732af3ce..49d6fa635a 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -3,7 +3,6 @@ import moment from 'moment'; import * as sender from '../../../../../website/server/libs/email'; import common from '../../../../../website/common'; import api from '../../../../../website/server/libs/payments/payments'; -import * as analytics from '../../../../../website/server/libs/analyticsService'; import * as notifications from '../../../../../website/server/libs/pushNotifications'; import { model as User } from '../../../../../website/server/models/user'; import { translate as t } from '../../../../helpers/api-integration/v3'; @@ -13,6 +12,7 @@ import { import * as worldState from '../../../../../website/server/libs/worldState'; import { TransactionModel } from '../../../../../website/server/models/transaction'; import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events'; +import { SubscriptionEventModel } from '../../../../../website/server/models/analytics/subscriptionEvent'; describe('payments/index', () => { let user; @@ -36,8 +36,6 @@ describe('payments/index', () => { sandbox.stub(sender, 'sendTxn'); sandbox.stub(user, 'sendMessage'); - sandbox.stub(analytics.mockAnalyticsService, 'trackPurchase'); - sandbox.stub(analytics.mockAnalyticsService, 'track'); sandbox.stub(notifications, 'sendNotification'); data = { @@ -97,6 +95,16 @@ describe('payments/index', () => { expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); }); + it('tracks subscription events', async () => { + await api.createSubscription(data); + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: recipient._id }); + expect(subscriptionEvent).to.exist; + expect(subscriptionEvent).to.have.property('eventType', 'subscribed'); + expect(subscriptionEvent).to.have.property('userId', recipient._id); + expect(subscriptionEvent).to.have.property('planId', 'basic_3mo'); + expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method'); + }); + it('adds extra months to an existing subscription', async () => { recipient.purchased.plan = plan; @@ -298,28 +306,6 @@ describe('payments/index', () => { expect(notifications.sendNotification).to.be.calledOnce; }); - it('tracks subscription purchase as gift', async () => { - await api.createSubscription(data); - - expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce; - expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({ - uuid: user._id, - groupId: undefined, - itemPurchased: 'Subscription', - sku: 'payment method-subscription', - purchaseType: 'subscribe', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: true, - purchaseValue: 15, - firstPurchase: true, - headers: { - 'x-client': 'habitica-web', - 'user-agent': '', - }, - }); - }); - context('No Active Promotion', () => { beforeEach(() => { sinon.stub(worldState, 'getCurrentEventList').returns([]); @@ -455,6 +441,16 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCreated).to.exist; }); + it('tracks subscription events', async () => { + await api.createSubscription(data); + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id }); + expect(subscriptionEvent).to.exist; + expect(subscriptionEvent).to.have.property('userId', user._id); + expect(subscriptionEvent).to.have.property('ipAddress'); + expect(subscriptionEvent).to.have.property('planId', 'basic_3mo'); + expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method'); + }); + it('sets plan.dateCreated if it did not previously exist', async () => { expect(user.purchased.plan.dateCreated).to.not.exist; @@ -543,29 +539,24 @@ describe('payments/index', () => { expect(sender.sendTxn).to.be.calledWith(data.user, 'subscription-begins'); }); - it('tracks subscription purchase', async () => { - await api.createSubscription(data); - - expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce; - expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({ - uuid: user._id, - groupId: undefined, - itemPurchased: 'Subscription', - sku: 'payment method-subscription', - purchaseType: 'subscribe', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: false, - purchaseValue: 15, - firstPurchase: true, - headers: { - 'x-client': 'habitica-web', - 'user-agent': '', - }, - }); - }); - context('Upgrades subscription', () => { + it('tracks subscription events', async () => { + data.sub.key = 'basic_earned'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + data.sub.key = 'basic_6mo'; + data.updatedFrom = { key: 'basic_earned' }; + await api.createSubscription(data); + + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_6mo' }); + expect(subscriptionEvent).to.exist; + expect(subscriptionEvent).to.have.property('eventType', 'upgraded'); + expect(subscriptionEvent).to.have.property('userId', user._id); + expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method'); + }); + it('from basic_earned to basic_6mo', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; @@ -608,6 +599,23 @@ describe('payments/index', () => { }); context('Downgrades subscription', () => { + it('tracks subscription events', async () => { + data.sub.key = 'basic_6mo'; + expect(user.purchased.plan.planId).to.not.exist; + + await api.createSubscription(data); + + data.sub.key = 'basic_earned'; + data.updatedFrom = { key: 'basic_6mo' }; + await api.createSubscription(data); + + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_earned' }); + expect(subscriptionEvent).to.exist; + expect(subscriptionEvent).to.have.property('eventType', 'downgraded'); + expect(subscriptionEvent).to.have.property('userId', user._id); + expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method'); + }); + it('from basic_6mo to basic_earned', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; @@ -1136,6 +1144,15 @@ describe('payments/index', () => { expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days }); + it('tracks subscription events', async () => { + await api.cancelSubscription(data); + + const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id }); + expect(subscriptionEvent).to.exist; + expect(subscriptionEvent).to.have.property('eventType', 'cancelled'); + expect(subscriptionEvent).to.have.property('userId', user._id); + }); + it('adds extraMonths to dateTerminated value', async () => { user.purchased.plan.extraMonths = 2; diff --git a/test/api/unit/middlewares/analytics.test.js b/test/api/unit/middlewares/analytics.test.js deleted file mode 100644 index 8ce68aee73..0000000000 --- a/test/api/unit/middlewares/analytics.test.js +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable global-require */ -import nconf from 'nconf'; -import requireAgain from 'require-again'; -import { - generateRes, - generateReq, - generateNext, -} from '../../../helpers/api-unit.helper'; -import * as analyticsService from '../../../../website/server/libs/analyticsService'; - -describe('analytics middleware', () => { - let res; let req; let - next; - const pathToAnalyticsMiddleware = '../../../../website/server/middlewares/analytics'; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - }); - - it('attaches analytics object to res', () => { - const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default; - - attachAnalytics(req, res, next); - - expect(res.analytics).to.exist; - }); - - it('attaches stubbed methods for non-prod environments', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); - const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default; - - attachAnalytics(req, res, next); - - expect(res.analytics.track).to.eql(analyticsService.mockAnalyticsService.track); - expect(res.analytics.trackPurchase).to.eql(analyticsService.mockAnalyticsService.trackPurchase); - }); - - it('attaches real methods for prod environments', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - - const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default; - - attachAnalytics(req, res, next); - - expect(res.analytics.track).to.eql(analyticsService.track); - expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase); - }); -}); diff --git a/test/api/v3/integration/analytics/POST-analytics_track_eventName.test.js b/test/api/v3/integration/analytics/POST-analytics_track_eventName.test.js deleted file mode 100644 index dd174fc108..0000000000 --- a/test/api/v3/integration/analytics/POST-analytics_track_eventName.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { - generateUser, - requester, -} from '../../../../helpers/api-integration/v3'; -import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService'; - -describe('POST /analytics/track/:eventName', () => { - it('calls res.analytics', async () => { - const user = await generateUser(); - sandbox.spy(analytics, 'track'); - - const requestWithHeaders = requester(user, { 'x-client': 'habitica-web' }); - await requestWithHeaders.post('/analytics/track/eventName', { data: 'example' }, { 'x-client': 'habitica-web' }); - expect(analytics.track).to.be.calledOnce; - expect(analytics.track).to.be.calledWith('eventName', sandbox.match({ data: 'example' })); - - sandbox.restore(); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_sleep.test.js b/test/api/v3/integration/user/POST-user_sleep.test.js index 9afb4a1956..9db5958663 100644 --- a/test/api/v3/integration/user/POST-user_sleep.test.js +++ b/test/api/v3/integration/user/POST-user_sleep.test.js @@ -1,7 +1,6 @@ import { generateUser, } from '../../../../helpers/api-integration/v3'; -import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService'; describe('POST /user/sleep', () => { let user; @@ -23,15 +22,4 @@ describe('POST /user/sleep', () => { await user.sync(); expect(user.preferences.sleep).to.be.false; }); - - it('sends sleep status to analytics service', async () => { - sandbox.spy(analytics, 'track'); - - await user.post('/user/sleep'); - await user.sync(); - expect(analytics.track).to.be.calledOnce; - expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep)); - - sandbox.restore(); - }); }); diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index eb22a02987..54ee542d30 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -9,6 +9,7 @@ import { } from '../../../../../helpers/api-integration/v3'; import { ApiUser } from '../../../../../helpers/api-integration/api-classes'; import { encrypt } from '../../../../../../website/server/libs/encryption'; +import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent'; function generateRandomUserName () { return (Date.now() + uuid()).substring(0, 20); @@ -41,6 +42,25 @@ describe('POST /user/auth/local/register', () => { expect(user.newUser).to.eql(true); }); + it('tracks a registration event', async () => { + const username = generateRandomUserName(); + const email = `${username}@example.com`; + const password = 'password'; + + const user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id }); + expect(registrationEvent).to.exist; + expect(registrationEvent).to.have.property('userId', user._id); + expect(registrationEvent).to.have.property('ipAddress'); + expect(registrationEvent).to.have.property('authenticationMethod', 'local'); + }); + it('registers a new user and sets verifiedUsername to true', async () => { const username = generateRandomUserName(); const email = `${username}@example.com`; diff --git a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js index a447bf68d7..4a12b09d65 100644 --- a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js +++ b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js @@ -7,6 +7,7 @@ import { getProperty, } from '../../../../../helpers/api-integration/v3'; import apiErrorMessages from '../../../../../../website/common/script/errors/apiErrorMessages'; +import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent'; describe('POST /user/auth/social', () => { let api; @@ -65,6 +66,19 @@ describe('POST /user/auth/social', () => { await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user'); }); + it('tracks a registration event', async () => { + const socialUser = await api.post(endpoint, { + authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase + network, + }); + + const registrationEvent = await RegistrationEventModel.findOne({ userId: socialUser.id }); + expect(registrationEvent).to.exist; + expect(registrationEvent).to.have.property('userId', socialUser.id); + expect(registrationEvent).to.have.property('ipAddress'); + expect(registrationEvent).to.have.property('authenticationMethod', 'google'); + }); + it('includes sanitized version of provided username', async () => { const response = await api.post(endpoint, { authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase @@ -231,6 +245,17 @@ describe('POST /user/auth/social', () => { expect(response.newUser).to.be.false; }); + it('does not track a registration event for existing users', async () => { + const beforeEvents = await RegistrationEventModel.find({ userId: user._id }); + await user.post(endpoint, { + authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase + network, + }); + + const registrationEvents = await RegistrationEventModel.find({ userId: user._id }); + expect(registrationEvents).to.have.lengthOf(beforeEvents.length); + }); + it('does not log into other account if social auth already exists', async () => { const registerResponse = await api.post(endpoint, { authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase diff --git a/test/common/ops/buy/buy.test.js b/test/common/ops/buy/buy.test.js index ff553f1aac..500068706f 100644 --- a/test/common/ops/buy/buy.test.js +++ b/test/common/ops/buy/buy.test.js @@ -13,7 +13,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag describe('shared.ops.buy', () => { let user; - const analytics = { track () {} }; beforeEach(() => { user = generateUser({ @@ -32,12 +31,6 @@ describe('shared.ops.buy', () => { }, }, }); - - sinon.stub(analytics, 'track'); - }); - - afterEach(() => { - analytics.track.restore(); }); it('returns error when key is not provided', async () => { @@ -51,10 +44,8 @@ describe('shared.ops.buy', () => { it('buys health potion', async () => { user.stats.hp = 30; - await buy(user, { params: { key: 'potion' } }, analytics); + await buy(user, { params: { key: 'potion' } }); expect(user.stats.hp).to.eql(45); - - expect(analytics.track).to.be.calledOnce; }); it('adds equipment to inventory', async () => { diff --git a/test/common/ops/buy/buyArmoire.test.js b/test/common/ops/buy/buyArmoire.test.js index 172625a9f6..0a0694e15f 100644 --- a/test/common/ops/buy/buyArmoire.test.js +++ b/test/common/ops/buy/buyArmoire.test.js @@ -29,10 +29,9 @@ describe('shared.ops.buyArmoire', () => { const YIELD_EQUIPMENT = 0.5; const YIELD_FOOD = 0.7; const YIELD_EXP = 0.9; - const analytics = { track () {} }; - async function buyArmoire (_user, _req, _analytics) { - const buyOp = new BuyArmoireOperation(_user, _req, _analytics); + async function buyArmoire (_user, _req) { + const buyOp = new BuyArmoireOperation(_user, _req); return buyOp.purchase(); } @@ -50,12 +49,10 @@ describe('shared.ops.buyArmoire', () => { user.items.food = {}; sandbox.stub(randomValFns, 'trueRandom'); - sinon.stub(analytics, 'track'); }); afterEach(() => { randomValFns.trueRandom.restore(); - analytics.track.restore(); }); context('failure conditions', () => { @@ -147,7 +144,7 @@ describe('shared.ops.buyArmoire', () => { expect(_.size(user.items.gear.owned)).to.equal(2); - await buyArmoire(user, {}, analytics); + await buyArmoire(user, {}); expect(_.size(user.items.gear.owned)).to.equal(3); @@ -155,7 +152,6 @@ describe('shared.ops.buyArmoire', () => { expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2); expect(user.stats.gp).to.eql(100); - expect(analytics.track).to.be.calledTwice; }); }); }); diff --git a/test/common/ops/buy/buyGem.test.js b/test/common/ops/buy/buyGem.test.js index 0ffdb131f7..c050e45a57 100644 --- a/test/common/ops/buy/buyGem.test.js +++ b/test/common/ops/buy/buyGem.test.js @@ -1,6 +1,5 @@ /* eslint-disable camelcase */ -import sinon from 'sinon'; // eslint-disable-line no-shadow import { generateUser, } from '../../../helpers/common.helper'; @@ -11,15 +10,14 @@ import i18n from '../../../../website/common/script/i18n'; import { BuyGemOperation } from '../../../../website/common/script/ops/buy/buyGem'; import planGemLimits from '../../../../website/common/script/libs/planGemLimits'; -async function buyGem (user, req, analytics) { - const buyOp = new BuyGemOperation(user, req, analytics); +async function buyGem (user, req) { + const buyOp = new BuyGemOperation(user, req); return buyOp.purchase(); } describe('shared.ops.buyGem', () => { let user; - const analytics = { track () {} }; const goldPoints = 40; const gemsBought = 40; const userGemAmount = 10; @@ -35,23 +33,16 @@ describe('shared.ops.buyGem', () => { }, }, }); - - sinon.stub(analytics, 'track'); - }); - - afterEach(() => { - analytics.track.restore(); }); context('Gems', () => { it('purchases gems', async () => { - const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } }, analytics); + const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } }); expect(message).to.equal(i18n.t('plusGem', { count: 1 })); expect(user.balance).to.equal(userGemAmount + 0.25); expect(user.purchased.plan.gemsBought).to.equal(1); expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); - expect(analytics.track).to.be.calledOnce; }); it('purchases gems with a different language than the default', async () => { diff --git a/test/common/ops/buy/buyHealthPotion.test.js b/test/common/ops/buy/buyHealthPotion.test.js index b7449bb3d6..af458456ea 100644 --- a/test/common/ops/buy/buyHealthPotion.test.js +++ b/test/common/ops/buy/buyHealthPotion.test.js @@ -10,10 +10,9 @@ import i18n from '../../../../website/common/script/i18n'; describe('shared.ops.buyHealthPotion', () => { let user; - const analytics = { track () {} }; - async function buyHealthPotion (_user, _req, _analytics) { - const buyOp = new BuyHealthPotionOperation(_user, _req, _analytics); + async function buyHealthPotion (_user, _req) { + const buyOp = new BuyHealthPotionOperation(_user, _req); return buyOp.purchase(); } @@ -32,19 +31,13 @@ describe('shared.ops.buyHealthPotion', () => { }, stats: { gp: 200 }, }); - sinon.stub(analytics, 'track'); - }); - - afterEach(() => { - analytics.track.restore(); }); context('Potion', () => { it('recovers 15 hp', async () => { user.stats.hp = 30; - await buyHealthPotion(user, {}, analytics); + await buyHealthPotion(user, {}); expect(user.stats.hp).to.eql(45); - expect(analytics.track).to.be.calledOnce; }); it('does not increase hp above 50', async () => { diff --git a/test/common/ops/buy/buyMarketGear.test.js b/test/common/ops/buy/buyMarketGear.test.js index 82d9b9ef53..f5aef4ff0c 100644 --- a/test/common/ops/buy/buyMarketGear.test.js +++ b/test/common/ops/buy/buyMarketGear.test.js @@ -13,15 +13,14 @@ import { import i18n from '../../../../website/common/script/i18n'; import { errorMessage } from '../../../../website/common/script/libs/errorMessage'; -async function buyGear (user, req, analytics) { - const buyOp = new BuyMarketGearOperation(user, req, analytics); +async function buyGear (user, req) { + const buyOp = new BuyMarketGearOperation(user, req); return buyOp.purchase(); } describe('shared.ops.buyMarketGear', () => { let user; - const analytics = { track () {} }; let clock; beforeEach(() => { @@ -47,14 +46,12 @@ describe('shared.ops.buyMarketGear', () => { sinon.stub(shared, 'randomVal'); sinon.stub(shared.onboarding, 'checkOnboardingStatus'); sinon.stub(shared.fns, 'predictableRandom'); - sinon.stub(analytics, 'track'); }); afterEach(() => { shared.randomVal.restore(); shared.fns.predictableRandom.restore(); shared.onboarding.checkOnboardingStatus.restore(); - analytics.track.restore(); if (clock) { clock.restore(); @@ -65,7 +62,7 @@ describe('shared.ops.buyMarketGear', () => { it('adds equipment to inventory', async () => { user.stats.gp = 31; - await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics); + await buyGear(user, { params: { key: 'armor_warrior_1' } }); expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, @@ -92,13 +89,12 @@ describe('shared.ops.buyMarketGear', () => { eyewear_special_whiteHalfMoon: true, eyewear_special_yellowHalfMoon: true, }); - expect(analytics.track).to.be.calledOnce; }); it('adds the onboarding achievement to the user and checks the onboarding status', async () => { user.stats.gp = 31; - await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics); + await buyGear(user, { params: { key: 'armor_warrior_1' } }); expect(user.addAchievement).to.be.calledOnce; expect(user.addAchievement).to.be.calledWith('purchasedEquipment'); @@ -111,7 +107,7 @@ describe('shared.ops.buyMarketGear', () => { user.stats.gp = 31; user.achievements.purchasedEquipment = true; - await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics); + await buyGear(user, { params: { key: 'armor_warrior_1' } }); expect(user.addAchievement).to.not.be.called; }); diff --git a/test/common/ops/buy/buyMysterySet.test.js b/test/common/ops/buy/buyMysterySet.test.js index f2d74046ba..4ee986489e 100644 --- a/test/common/ops/buy/buyMysterySet.test.js +++ b/test/common/ops/buy/buyMysterySet.test.js @@ -14,7 +14,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag describe('shared.ops.buyMysterySet', () => { let user; - const analytics = { track () {} }; let clock; beforeEach(() => { @@ -27,11 +26,9 @@ describe('shared.ops.buyMysterySet', () => { }, }, }); - sinon.stub(analytics, 'track'); }); afterEach(() => { - analytics.track.restore(); if (clock) { clock.restore(); } @@ -93,7 +90,7 @@ describe('shared.ops.buyMysterySet', () => { context('successful purchases', () => { it('buys Steampunk Accessories Set', async () => { user.purchased.plan.consecutive.trinkets = 1; - await buyMysterySet(user, { params: { key: '301404' } }, analytics); + await buyMysterySet(user, { params: { key: '301404' } }); expect(user.purchased.plan.consecutive.trinkets).to.eql(0); expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true); @@ -106,7 +103,7 @@ describe('shared.ops.buyMysterySet', () => { it('buys mystery set if it is available', async () => { clock = sinon.useFakeTimers(new Date('2024-01-16')); user.purchased.plan.consecutive.trinkets = 1; - await buyMysterySet(user, { params: { key: '201601' } }, analytics); + await buyMysterySet(user, { params: { key: '201601' } }); expect(user.purchased.plan.consecutive.trinkets).to.eql(0); expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true); diff --git a/test/common/ops/buy/buyQuestGems.test.js b/test/common/ops/buy/buyQuestGems.test.js index aa5080a526..e80df7565a 100644 --- a/test/common/ops/buy/buyQuestGems.test.js +++ b/test/common/ops/buy/buyQuestGems.test.js @@ -12,10 +12,9 @@ describe('shared.ops.buyQuestGems', () => { let user; let clock; const goldPoints = 40; - const analytics = { track () {} }; - async function buyQuest (_user, _req, _analytics) { - const buyOp = new BuyQuestWithGemOperation(_user, _req, _analytics); + async function buyQuest (_user, _req) { + const buyOp = new BuyQuestWithGemOperation(_user, _req); return buyOp.purchase(); } @@ -25,13 +24,11 @@ describe('shared.ops.buyQuestGems', () => { }); beforeEach(() => { - sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); clock = sinon.useFakeTimers(new Date('2024-01-16')); }); afterEach(() => { - analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); clock.restore(); }); diff --git a/test/common/ops/buy/buyQuestGold.test.js b/test/common/ops/buy/buyQuestGold.test.js index a87d800efe..b83674f503 100644 --- a/test/common/ops/buy/buyQuestGold.test.js +++ b/test/common/ops/buy/buyQuestGold.test.js @@ -12,21 +12,15 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag describe('shared.ops.buyQuest', () => { let user; - const analytics = { track () {} }; - async function buyQuest (_user, _req, _analytics) { - const buyOp = new BuyQuestWithGoldOperation(_user, _req, _analytics); + async function buyQuest (_user, _req) { + const buyOp = new BuyQuestWithGoldOperation(_user, _req); return buyOp.purchase(); } beforeEach(() => { user = generateUser(); - sinon.stub(analytics, 'track'); - }); - - afterEach(() => { - analytics.track.restore(); }); it('buys a Quest scroll', async () => { @@ -35,12 +29,11 @@ describe('shared.ops.buyQuest', () => { params: { key: 'dilatoryDistress1', }, - }, analytics); + }); expect(user.items.quests).to.eql({ dilatoryDistress1: 1, }); expect(user.stats.gp).to.equal(5); - expect(analytics.track).to.be.calledOnce; }); it('if a user\'s count of a quest scroll is negative, it will be reset to 0 before incrementing when they buy a new one.', async () => { @@ -49,10 +42,9 @@ describe('shared.ops.buyQuest', () => { user.items.quests[key] = -1; await buyQuest(user, { params: { key }, - }, analytics); + }); expect(user.items.quests[key]).to.equal(1); expect(user.stats.gp).to.equal(5); - expect(analytics.track).to.be.calledOnce; }); it('buys a Quest scroll with the right quantity if a string is passed for quantity', async () => { @@ -61,13 +53,13 @@ describe('shared.ops.buyQuest', () => { params: { key: 'dilatoryDistress1', }, - }, analytics); + }); await buyQuest(user, { params: { key: 'dilatoryDistress1', }, quantity: '3', - }, analytics); + }); expect(user.items.quests).to.eql({ dilatoryDistress1: 4, @@ -82,7 +74,7 @@ describe('shared.ops.buyQuest', () => { key: 'dilatoryDistress1', }, quantity: 'a', - }, analytics); + }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidQuantity')); @@ -187,12 +179,11 @@ describe('shared.ops.buyQuest', () => { params: { key: 'dilatoryDistress3', }, - }, analytics); + }); expect(user.items.quests).to.eql({ dilatoryDistress3: 1, }); expect(user.stats.gp).to.equal(100); - expect(analytics.track).to.be.calledOnce; }); }); diff --git a/test/common/ops/buy/buySpell.test.js b/test/common/ops/buy/buySpell.test.js index f914ca8c6f..52da57ecde 100644 --- a/test/common/ops/buy/buySpell.test.js +++ b/test/common/ops/buy/buySpell.test.js @@ -14,20 +14,17 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag describe('shared.ops.buySpecialSpell', () => { let user; let clock; - const analytics = { track () {} }; - async function buySpecialSpell (_user, _req, _analytics) { - const buyOp = new BuySpellOperation(_user, _req, _analytics); + async function buySpecialSpell (_user, _req) { + const buyOp = new BuySpellOperation(_user, _req); return buyOp.purchase(); } beforeEach(() => { user = generateUser(); - sinon.stub(analytics, 'track'); }); afterEach(() => { - analytics.track.restore(); if (clock) { clock.restore(); } @@ -78,7 +75,7 @@ describe('shared.ops.buySpecialSpell', () => { params: { key: 'thankyou', }, - }, analytics); + }); expect(user.stats.gp).to.equal(1); expect(user.items.special.thankyou).to.equal(1); @@ -89,7 +86,6 @@ describe('shared.ops.buySpecialSpell', () => { expect(message).to.equal(i18n.t('messageBought', { itemText: item.text(), })); - expect(analytics.track).to.be.calledOnce; }); it('buys a limited card when it is available', async () => { @@ -101,7 +97,7 @@ describe('shared.ops.buySpecialSpell', () => { params: { key: 'nye', }, - }, analytics); + }); expect(user.stats.gp).to.equal(1); expect(user.items.special.nye).to.equal(1); @@ -112,7 +108,6 @@ describe('shared.ops.buySpecialSpell', () => { expect(message).to.equal(i18n.t('messageBought', { itemText: item.text(), })); - expect(analytics.track).to.be.calledOnce; }); it('throws an error if the card is not currently available', async () => { @@ -140,7 +135,7 @@ describe('shared.ops.buySpecialSpell', () => { params: { key: 'seafoam', }, - }, analytics); + }); expect(user.stats.gp).to.equal(1); expect(user.items.special.seafoam).to.equal(1); @@ -151,7 +146,6 @@ describe('shared.ops.buySpecialSpell', () => { expect(message).to.equal(i18n.t('messageBought', { itemText: item.text(), })); - expect(analytics.track).to.be.calledOnce; }); it('throws an error if the spell is not currently available', async () => { diff --git a/test/common/ops/buy/hourglassPurchase.test.js b/test/common/ops/buy/hourglassPurchase.test.js index 34af73f33e..4075333f92 100644 --- a/test/common/ops/buy/hourglassPurchase.test.js +++ b/test/common/ops/buy/hourglassPurchase.test.js @@ -13,21 +13,15 @@ import { BuyHourglassMountOperation } from '../../../../website/common/script/op describe('common.ops.hourglassPurchase', () => { let user; - const analytics = { track () {} }; - async function buyMount (_user, _req, _analytics) { - const buyOp = new BuyHourglassMountOperation(_user, _req, _analytics); + async function buyMount (_user, _req) { + const buyOp = new BuyHourglassMountOperation(_user, _req); return buyOp.purchase(); } beforeEach(() => { user = generateUser(); - sinon.stub(analytics, 'track'); - }); - - afterEach(() => { - analytics.track.restore(); }); context('failure conditions', () => { @@ -131,12 +125,11 @@ describe('common.ops.hourglassPurchase', () => { it('buys a pet', async () => { user.purchased.plan.consecutive.trinkets = 2; - const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } }, analytics); + const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } }); expect(message).to.eql(i18n.t('hourglassPurchase')); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); expect(user.items.pets).to.eql({ 'MantisShrimp-Base': 5 }); - expect(analytics.track).to.be.calledOnce; }); it('buys a mount', async () => { diff --git a/test/common/ops/buy/purchase.test.js b/test/common/ops/buy/purchase.test.js index 9fc35af020..b5d22f48b1 100644 --- a/test/common/ops/buy/purchase.test.js +++ b/test/common/ops/buy/purchase.test.js @@ -17,20 +17,17 @@ describe('shared.ops.purchase', () => { let user; let clock; const goldPoints = 40; - const analytics = { track () {} }; before(() => { user = generateUser({ 'stats.class': 'rogue' }); }); beforeEach(() => { - sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); clock = sandbox.useFakeTimers(new Date('2024-01-10')); }); afterEach(() => { - analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); clock.restore(); }); @@ -187,11 +184,10 @@ describe('shared.ops.purchase', () => { const type = 'eggs'; const key = 'Wolf'; - await purchase(user, { params: { type, key } }, analytics); + await purchase(user, { params: { type, key } }); expect(user.items[type][key]).to.equal(1); expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true); - expect(analytics.track).to.be.calledOnce; }); it('purchases hatchingPotions', async () => { @@ -332,7 +328,7 @@ describe('shared.ops.purchase', () => { const key = 'Wolf'; try { - await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics); + await purchase(user, { params: { type, key }, quantity: 'jamboree' }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidQuantity')); @@ -345,7 +341,7 @@ describe('shared.ops.purchase', () => { user.balance = 10; try { - await purchase(user, { params: { type, key }, quantity: -2 }, analytics); + await purchase(user, { params: { type, key }, quantity: -2 }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidQuantity')); @@ -358,7 +354,7 @@ describe('shared.ops.purchase', () => { user.balance = 10; try { - await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics); + await purchase(user, { params: { type, key }, quantity: 2.9 }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidQuantity')); diff --git a/test/helpers/api-integration/requester.js b/test/helpers/api-integration/requester.js index 61c0b2fabf..a1f86f7752 100644 --- a/test/helpers/api-integration/requester.js +++ b/test/helpers/api-integration/requester.js @@ -40,7 +40,6 @@ function _requestMaker (user, method, additionalSets = {}) { || route.indexOf('/paypal') === 0 || route.indexOf('/amazon') === 0 || route.indexOf('/stripe') === 0 - || route.indexOf('/analytics') === 0 ) { url += `${route}`; } else { diff --git a/website/client/src/components/group-plans/taskInformation.vue b/website/client/src/components/group-plans/taskInformation.vue index 1e49b5c5c3..6bbb455fa6 100644 --- a/website/client/src/components/group-plans/taskInformation.vue +++ b/website/client/src/components/group-plans/taskInformation.vue @@ -198,7 +198,6 @@ import dailyIcon from '@/assets/svg/daily.svg?raw'; import todoIcon from '@/assets/svg/todo.svg?raw'; import rewardIcon from '@/assets/svg/reward.svg?raw'; -import * as Analytics from '@/libs/analytics'; import { mapState } from '@/libs/store'; export default { @@ -438,14 +437,6 @@ export default { return false; }, changeMirrorPreference (newVal) { - Analytics.track({ - eventName: 'mirror tasks', - eventAction: 'mirror tasks', - eventCategory: 'behavior', - hitType: 'event', - mirror: newVal, - group: this.group._id, - }, { trackOnClient: true }); const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || []; if (newVal) { // we're turning copy ON for this group groupsToMirror.push(this.group._id); diff --git a/website/client/src/components/groups/createPartyModal.vue b/website/client/src/components/groups/createPartyModal.vue index 2483a38af2..081989c74f 100644 --- a/website/client/src/components/groups/createPartyModal.vue +++ b/website/client/src/components/groups/createPartyModal.vue @@ -240,7 +240,6 @@