mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-21 19:59:57 -05:00
Compare commits
335 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b57fb94579 | |||
| 42805a2792 | |||
| d7e7668255 | |||
| a4fda59a69 | |||
| 3b9ffae625 | |||
| dd413518f5 | |||
| c581b88213 | |||
| 0078d8f2b2 | |||
| 54ea0aab18 | |||
| 24b2a5beb8 | |||
| fe9332dff4 | |||
| c6582e4c3c | |||
| b4b7980eee | |||
| 6069fbd61f | |||
| 38a591bdd1 | |||
| 2736d8acf3 | |||
| 8fe13dbb23 | |||
| 4581bb9315 | |||
| 2999212379 | |||
| d2bd246e6e | |||
| 1482f6c225 | |||
| 1178da3a26 | |||
| 819ed2b355 | |||
| a92999fc11 | |||
| 3489b88752 | |||
| 94bda30385 | |||
| e8bbdc2cb8 | |||
| d465efaf96 | |||
| 3a08de7ab3 | |||
| e6ffd69148 | |||
| 746fcfff49 | |||
| 8aa343d390 | |||
| d80c43c82a | |||
| 80e4b8617a | |||
| b3fac011a9 | |||
| 8f4d871911 | |||
| 19373ce84d | |||
| 55bfca20d9 | |||
| 2ee2b05d1c | |||
| 3d3db1bdd9 | |||
| a5dff99fa1 | |||
| 25435218e1 | |||
| 7746049bb4 | |||
| 036bf43cf3 | |||
| c4e0127a37 | |||
| 7ae059e243 | |||
| 81b9a0b92d | |||
| a6e87452a6 | |||
| c40d384913 | |||
| 65144aef28 | |||
| cc7683a871 | |||
| 31b2781333 | |||
| d37d3bc5ac | |||
| ef3a28791e | |||
| c3c2607bca | |||
| 7a6d64f158 | |||
| 836e63246d | |||
| 61585b2549 | |||
| 07275bd522 | |||
| 74fc543ef2 | |||
| 8c90e5472b | |||
| c8d9ba6c8e | |||
| 1675c2749b | |||
| e85a2bae14 | |||
| 3355500fba | |||
| 486f15df0f | |||
| f0b6b5611c | |||
| 7e45c79714 | |||
| 8da6065355 | |||
| a212363bda | |||
| 2e19e73b9e | |||
| 1047b0e03b | |||
| 159f850bd1 | |||
| 42083efb7e | |||
| f21e800b0b | |||
| 40122e5621 | |||
| 0ae19d9107 | |||
| 68bfebcf30 | |||
| 3e93911e70 | |||
| 4ea8636f03 | |||
| 9f97a09b8c | |||
| eccc115b73 | |||
| 2b26eb2bd1 | |||
| 8e042cabc4 | |||
| 8abe167848 | |||
| 3414f962e2 | |||
| 1b68e6d4d3 | |||
| 5dd9711413 | |||
| a542277a41 | |||
| cdf8556fd6 | |||
| 3d93390a7a | |||
| 59f9cfa0f4 | |||
| 80d7804f69 | |||
| 4e5efe09a3 | |||
| d42a597672 | |||
| ea17b2e9c7 | |||
| f56708cd88 | |||
| 005d14f6e8 | |||
| c05a96ce6c | |||
| 8fdbfb9dc6 | |||
| 057a642baa | |||
| 6c522157a7 | |||
| ba9a1ab2a9 | |||
| 4767461c4f | |||
| 847c97dc8f | |||
| 215b26acac | |||
| e223e7821a | |||
| 8134fa7c00 | |||
| 84208f612e | |||
| 57e06334c0 | |||
| be695d25b3 | |||
| 77ee83f467 | |||
| 86556e346b | |||
| 2007a872c6 | |||
| a8348038de | |||
| 87bcd69979 | |||
| 8f8e84d0c7 | |||
| 2c18cb00cc | |||
| daa0fd18c0 | |||
| 5c555cbf88 | |||
| 7379c7b230 | |||
| c055537c38 | |||
| 7559feec8e | |||
| 43808696a8 | |||
| 72fb41c7e0 | |||
| 3bf18e09ed | |||
| 407a901883 | |||
| 81a008906b | |||
| 992a978923 | |||
| a8062ad615 | |||
| 781a904583 | |||
| d87946d912 | |||
| 7456ff2def | |||
| e0af620b40 | |||
| bb295551b5 | |||
| fce400f323 | |||
| c0ffb8b968 | |||
| 72539f9ba3 | |||
| dabd466719 | |||
| 8bf2304330 | |||
| 6937dc4e4e | |||
| 2917955ef0 | |||
| 55d13e44d4 | |||
| 90096f995f | |||
| 5c74c2b914 | |||
| 1f1a44e16f | |||
| a275109a3e | |||
| c65457690b | |||
| f740f12b97 | |||
| 9fd0bfae46 | |||
| bee23efbef | |||
| a504b18ce4 | |||
| f556b102c6 | |||
| ac62de7bd8 | |||
| 5ff3cc35a6 | |||
| 215e5e1c40 | |||
| 02ca96ea51 | |||
| e70ae4e9aa | |||
| e2bf8ae493 | |||
| 931a70a797 | |||
| e2d2a05315 | |||
| be041f734d | |||
| c430d2279c | |||
| ef592cf35f | |||
| f24cd10a79 | |||
| 2cd4e45016 | |||
| 8aaff7ae23 | |||
| 69a9fb89ef | |||
| e8eeb76cab | |||
| 2029739a1b | |||
| 5cef106ea5 | |||
| e096d7ac42 | |||
| 6db998e726 | |||
| 29c658b042 | |||
| 66710b8f38 | |||
| c77db3d625 | |||
| c947fa97d9 | |||
| b2b9702797 | |||
| e92503f032 | |||
| 8faa5b0582 | |||
| 95494c685b | |||
| 10978d46ab | |||
| 447eb6a0c4 | |||
| 3dec49b72c | |||
| 472d03f276 | |||
| fd9a27c3ab | |||
| a5c1423837 | |||
| e9829b8b60 | |||
| 7ecb83dc7e | |||
| e8ffe2286c | |||
| fe63436a57 | |||
| 5b93b9b37a | |||
| 1d55027791 | |||
| 83f0984da1 | |||
| 53d4f75cab | |||
| da45eb2adf | |||
| 3bf4af8d8b | |||
| f030691fac | |||
| 1f94e51693 | |||
| 86e7d7a72b | |||
| 140b852e03 | |||
| 8f949ce1cc | |||
| 5e21285370 | |||
| 7a65bc2d8d | |||
| a32fadbcbd | |||
| 305192ed1f | |||
| 7644e202c9 | |||
| d11c8442ef | |||
| d8b5391425 | |||
| dd287cd719 | |||
| e809d1f6e4 | |||
| da90fa6aaf | |||
| 77392db25a | |||
| 1bc1bf0621 | |||
| 635a258d62 | |||
| 384fb505c1 | |||
| 3e0bc36373 | |||
| 0a431afaaf | |||
| 8c911bcd41 | |||
| dcb7ac5955 | |||
| fb730942a0 | |||
| 9c92bf73f5 | |||
| 58f195fdb7 | |||
| 4b86c9c8a7 | |||
| 4cc689ec63 | |||
| 8690484f5e | |||
| 1f3e5b7a76 | |||
| 61c790f291 | |||
| b3440fa3a8 | |||
| a3f1835d1d | |||
| 9226f6f70e | |||
| 1130f9957f | |||
| ad1fd03aad | |||
| 6c93033ad2 | |||
| dd97b11b60 | |||
| 59ba07d4f3 | |||
| d2bfd1e3a9 | |||
| a8264bf526 | |||
| f202f2b3d3 | |||
| 4ea9f8282e | |||
| 205d84a111 | |||
| 5810853cc2 | |||
| 4547204bd8 | |||
| f17a0c91a3 | |||
| 16e1523b08 | |||
| 0f06ec1ab8 | |||
| 641266122a | |||
| 5ba939ee9c | |||
| c979e568f1 | |||
| 93f0c240f9 | |||
| ad04b077a4 | |||
| 7ffc454320 | |||
| dae0fbff16 | |||
| 5648092112 | |||
| 275b15b773 | |||
| 1025635e34 | |||
| 836cbdb81e | |||
| be922de7ba | |||
| 3a2f5e724d | |||
| 8a105c6a14 | |||
| 7f1c64a50e | |||
| 125f472f34 | |||
| bafd273475 | |||
| 365cb1c2eb | |||
| 876d5a67d6 | |||
| 3078af8f2a | |||
| dad1440138 | |||
| 12773d539e | |||
| ae4130b108 | |||
| ad0614282e | |||
| 5a7704aed7 | |||
| 2feadd6125 | |||
| efe0b3cd9e | |||
| 96731da380 | |||
| 0c5dd5d8b5 | |||
| 2f943a22e6 | |||
| 666184d7e4 | |||
| 17d22dda3f | |||
| d1a18c121d | |||
| 836d7f3991 | |||
| ace9c3c46a | |||
| 068640311e | |||
| f26d2a59ae | |||
| 03c7e9172e | |||
| 6fdc072ec3 | |||
| e68661c04b | |||
| 4f567592ea | |||
| 63c9b7a894 | |||
| eaec39188e | |||
| ba6940eb81 | |||
| f8a3e4d673 | |||
| 2727da6f6c | |||
| fa97852e38 | |||
| 2c7da25a25 | |||
| 9a072e3e76 | |||
| 823b339d27 | |||
| fe98d9485d | |||
| 407e1bb560 | |||
| 98a6535dc3 | |||
| 9948e8ee44 | |||
| bce07ec357 | |||
| 836807aa1e | |||
| ebbcbef6d5 | |||
| ccc6c9867f | |||
| 20d31ed8c8 | |||
| 39ff6cbe05 | |||
| 1bf2efa885 | |||
| 4b45a6389c | |||
| 5ba7d2395e | |||
| 972f23e235 | |||
| 9f599b0c8e | |||
| b937c2df0b | |||
| 9c4396027a | |||
| 2bab20d032 | |||
| cb2ee670e3 | |||
| b65d23d535 | |||
| 007cdf0ca2 | |||
| 1e4799bac6 | |||
| 47222445ad | |||
| 126b382da1 | |||
| ec78831a81 | |||
| 9bfb2afd9c | |||
| 389124b83f | |||
| eb25330296 | |||
| 29892ff5e3 | |||
| 99a31b322a | |||
| 1884c6c751 | |||
| 9456477953 | |||
| e3512a2bdd | |||
| 6ce3f84458 | |||
| 484c3cbac8 | |||
| c199beaf8c | |||
| 553aa01c25 | |||
| 8d1b10e458 | |||
| 0eaee9b1e4 |
@@ -82,7 +82,7 @@ jobs:
|
|||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
- run: npm run test:sanity
|
- run: npm run test:sanity
|
||||||
|
|
||||||
common:
|
common:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -129,13 +129,13 @@ jobs:
|
|||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
- run: npm run test:content
|
- run: npm run test:content
|
||||||
|
|
||||||
api-unit:
|
api-unit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [21.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [7.0]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -144,11 +144,13 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
uses: supercharge/mongodb-github-action@1.3.0
|
uses: supercharge/mongodb-github-action@1.11.0
|
||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
|
||||||
- run: sudo apt update
|
- run: sudo apt update
|
||||||
- run: sudo apt -y install libkrb5-dev
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
@@ -158,15 +160,17 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
|
|
||||||
- run: npm run test:api:unit
|
- run: npm run test:api:unit
|
||||||
env:
|
env:
|
||||||
REQUIRES_SERVER=true: true
|
REQUIRES_SERVER=true: true
|
||||||
|
|
||||||
api-v3-integration:
|
api-v3-integration:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [21.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [7.0]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -176,10 +180,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
uses: supercharge/mongodb-github-action@1.3.0
|
uses: supercharge/mongodb-github-action@1.11.0
|
||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
|
||||||
- run: sudo apt update
|
- run: sudo apt update
|
||||||
- run: sudo apt -y install libkrb5-dev
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
@@ -189,15 +194,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
|
|
||||||
- run: npm run test:api-v3:integration
|
- run: npm run test:api-v3:integration
|
||||||
env:
|
env:
|
||||||
REQUIRES_SERVER=true: true
|
REQUIRES_SERVER=true: true
|
||||||
|
|
||||||
api-v4-integration:
|
api-v4-integration:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [21.x]
|
node-version: [21.x]
|
||||||
mongodb-version: [4.2]
|
mongodb-version: [7.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -207,10 +215,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
- name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
|
||||||
uses: supercharge/mongodb-github-action@1.3.0
|
uses: supercharge/mongodb-github-action@1.11.0
|
||||||
with:
|
with:
|
||||||
mongodb-version: ${{ matrix.mongodb-version }}
|
mongodb-version: ${{ matrix.mongodb-version }}
|
||||||
mongodb-replica-set: rs
|
mongodb-replica-set: rs
|
||||||
|
|
||||||
- run: sudo apt update
|
- run: sudo apt update
|
||||||
- run: sudo apt -y install libkrb5-dev
|
- run: sudo apt -y install libkrb5-dev
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
@@ -220,6 +229,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
|
|
||||||
- run: npm run test:api-v4:integration
|
- run: npm run test:api-v4:integration
|
||||||
env:
|
env:
|
||||||
REQUIRES_SERVER=true: true
|
REQUIRES_SERVER=true: true
|
||||||
|
|||||||
+1
-1
@@ -47,5 +47,5 @@ webpack.webstorm.config
|
|||||||
|
|
||||||
# mongodb replica set for local dev
|
# mongodb replica set for local dev
|
||||||
mongodb-*.tgz
|
mongodb-*.tgz
|
||||||
/mongodb-data*
|
/mongodb-*
|
||||||
/.nyc_output
|
/.nyc_output
|
||||||
|
|||||||
+26
-28
@@ -8,18 +8,26 @@
|
|||||||
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
|
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
|
||||||
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
||||||
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
||||||
|
"APPLE_AUTH_CLIENT_ID": "",
|
||||||
|
"APPLE_AUTH_KEY_ID": "",
|
||||||
|
"APPLE_AUTH_PRIVATE_KEY": "",
|
||||||
|
"APPLE_TEAM_ID": "",
|
||||||
"BASE_URL": "http://localhost:3000",
|
"BASE_URL": "http://localhost:3000",
|
||||||
|
"BLOCKED_IPS": "",
|
||||||
|
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
|
||||||
"CRON_SAFE_MODE": "false",
|
"CRON_SAFE_MODE": "false",
|
||||||
"CRON_SEMI_SAFE_MODE": "false",
|
"CRON_SEMI_SAFE_MODE": "false",
|
||||||
|
"DEBUG_ENABLED": "false",
|
||||||
"DISABLE_REQUEST_LOGGING": "true",
|
"DISABLE_REQUEST_LOGGING": "true",
|
||||||
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
|
|
||||||
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
|
|
||||||
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
|
|
||||||
"EMAIL_SERVER_AUTH_PASSWORD": "password",
|
"EMAIL_SERVER_AUTH_PASSWORD": "password",
|
||||||
"EMAIL_SERVER_AUTH_USER": "user",
|
"EMAIL_SERVER_AUTH_USER": "user",
|
||||||
"EMAIL_SERVER_URL": "http://example.com",
|
"EMAIL_SERVER_URL": "http://example.com",
|
||||||
|
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
|
||||||
|
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
|
||||||
|
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
|
||||||
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
|
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
|
||||||
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||||
|
"ENABLE_STACKDRIVER_TRACING": "false",
|
||||||
"FACEBOOK_KEY": "123456789012345",
|
"FACEBOOK_KEY": "123456789012345",
|
||||||
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
|
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
|
||||||
@@ -29,15 +37,16 @@
|
|||||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||||
"IGNORE_REDIRECT": "true",
|
"IGNORE_REDIRECT": "true",
|
||||||
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
|
"LIVELINESS_PROBE_KEY": "",
|
||||||
|
"LOG_AMPLITUDE_EVENTS": "false",
|
||||||
|
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
|
||||||
"LOGGLY_CLIENT_TOKEN": "token",
|
"LOGGLY_CLIENT_TOKEN": "token",
|
||||||
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||||
"LOGGLY_TOKEN": "example-token",
|
"LOGGLY_TOKEN": "example-token",
|
||||||
"LOG_REQUESTS_EXCESSIVE_MODE": "false",
|
|
||||||
"MAINTENANCE_MODE": "false",
|
"MAINTENANCE_MODE": "false",
|
||||||
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
|
|
||||||
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
|
|
||||||
"MONGODB_POOL_SIZE": "10",
|
"MONGODB_POOL_SIZE": "10",
|
||||||
"MONGODB_SOCKET_TIMEOUT": "20000",
|
"MONGODB_SOCKET_TIMEOUT": "20000",
|
||||||
|
"NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs&directConnection=true&readPreference=secondary",
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
||||||
@@ -55,44 +64,33 @@
|
|||||||
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"PORT": 3000,
|
"PORT": 3000,
|
||||||
"PUSH_CONFIGS_APN_ENABLED": "false",
|
"PUSH_CONFIGS_APN_ENABLED": "false",
|
||||||
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
|
|
||||||
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
|
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
|
||||||
|
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
|
||||||
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
|
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
|
||||||
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
|
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
|
||||||
|
"RATE_LIMITER_ENABLED": "false",
|
||||||
|
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||||
|
"REDIS_PASSWORD": "12345678",
|
||||||
|
"REDIS_PORT": "1234",
|
||||||
"S3_ACCESS_KEY_ID": "accessKeyId",
|
"S3_ACCESS_KEY_ID": "accessKeyId",
|
||||||
"S3_BUCKET": "bucket",
|
"S3_BUCKET": "bucket",
|
||||||
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
||||||
"SESSION_SECRET": "YOUR SECRET HERE",
|
|
||||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
|
||||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||||
|
"SESSION_SECRET": "YOUR SECRET HERE",
|
||||||
"SITE_HTTP_AUTH_ENABLED": "false",
|
"SITE_HTTP_AUTH_ENABLED": "false",
|
||||||
"SITE_HTTP_AUTH_PASSWORDS": "password,wordpass,passkey",
|
"SITE_HTTP_AUTH_PASSWORDS": "password,wordpass,passkey",
|
||||||
"SITE_HTTP_AUTH_USERNAMES": "admin,tester,contributor",
|
"SITE_HTTP_AUTH_USERNAMES": "admin,tester,contributor",
|
||||||
|
"SKIP_SSL_CHECK_KEY": "key",
|
||||||
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||||
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||||
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
||||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||||
|
"SLOW_REQUEST_THRESHOLD": 1000,
|
||||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||||
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
|
"STRIPE_WEBHOOKS_ENDPOINT_SECRET": "111111",
|
||||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs&directConnection=true&readPreference=secondary",
|
||||||
"WEB_CONCURRENCY": 1,
|
|
||||||
"SKIP_SSL_CHECK_KEY": "key",
|
|
||||||
"ENABLE_STACKDRIVER_TRACING": "false",
|
|
||||||
"APPLE_AUTH_PRIVATE_KEY": "",
|
|
||||||
"APPLE_TEAM_ID": "",
|
|
||||||
"APPLE_AUTH_CLIENT_ID": "",
|
|
||||||
"APPLE_AUTH_KEY_ID": "",
|
|
||||||
"BLOCKED_IPS": "",
|
|
||||||
"LOG_AMPLITUDE_EVENTS": "false",
|
|
||||||
"RATE_LIMITER_ENABLED": "false",
|
|
||||||
"LIVELINESS_PROBE_KEY": "",
|
|
||||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
|
||||||
"REDIS_PORT": "1234",
|
|
||||||
"REDIS_PASSWORD": "12345678",
|
|
||||||
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
|
|
||||||
"TIME_TRAVEL_ENABLED": "false",
|
"TIME_TRAVEL_ENABLED": "false",
|
||||||
"DEBUG_ENABLED": "false",
|
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
|
||||||
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
|
"WEB_CONCURRENCY": 1
|
||||||
"SLOW_REQUEST_THRESHOLD": 1000
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
services:
|
|
||||||
client:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile-Dev
|
|
||||||
command: ["npm", "run", "client:dev"]
|
|
||||||
depends_on:
|
|
||||||
- server
|
|
||||||
environment:
|
|
||||||
- BASE_URL=http://server:3000
|
|
||||||
networks:
|
|
||||||
- habitica
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- .:/usr/src/habitica
|
|
||||||
- /usr/src/habitica/node_modules
|
|
||||||
- /usr/src/habitica/website/client/node_modules
|
|
||||||
server:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile-Dev
|
|
||||||
command: ["npm", "start"]
|
|
||||||
depends_on:
|
|
||||||
mongo:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
|
||||||
networks:
|
|
||||||
- habitica
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
volumes:
|
|
||||||
- .:/usr/src/habitica
|
|
||||||
- /usr/src/habitica/node_modules
|
|
||||||
mongo:
|
|
||||||
image: mongo:5.0.23
|
|
||||||
restart: unless-stopped
|
|
||||||
command: ["--replSet", "rs", "--bind_ip_all", "--port", "27017"]
|
|
||||||
healthcheck:
|
|
||||||
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
|
|
||||||
interval: 10s
|
|
||||||
timeout: 30s
|
|
||||||
start_period: 0s
|
|
||||||
start_interval: 1s
|
|
||||||
retries: 30
|
|
||||||
networks:
|
|
||||||
- habitica
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
networks:
|
|
||||||
habitica:
|
|
||||||
driver: bridge
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
networks:
|
||||||
|
mongodb-network:
|
||||||
|
name: "mongodb-network"
|
||||||
|
driver: bridge
|
||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: "mongo:7.0"
|
||||||
|
container_name: "habitica-mongodb-only"
|
||||||
|
networks:
|
||||||
|
- mongodb-network
|
||||||
|
hostname: "mongodb"
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
volumes:
|
||||||
|
- "./mongodb-data-docker:/data/db"
|
||||||
|
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
|
||||||
|
healthcheck:
|
||||||
|
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
|
||||||
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
start_period: 0s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
networks:
|
||||||
|
mongodb-network:
|
||||||
|
name: "mongodb-network"
|
||||||
|
driver: bridge
|
||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: "mongo:7.0"
|
||||||
|
container_name: "habitica-mongodb-test"
|
||||||
|
networks:
|
||||||
|
- mongodb-network
|
||||||
|
hostname: "mongodb"
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
volumes:
|
||||||
|
- "./mongodb-data-docker-testing:/data/db"
|
||||||
|
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
|
||||||
|
healthcheck:
|
||||||
|
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
|
||||||
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
start_period: 0s
|
||||||
|
retries: 30
|
||||||
+43
-22
@@ -1,35 +1,56 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
client:
|
client:
|
||||||
build: .
|
build:
|
||||||
networks:
|
context: .
|
||||||
- habitica
|
dockerfile: ./Dockerfile-Dev
|
||||||
environment:
|
command: ["npm", "run", "client:dev:docker"]
|
||||||
- BASE_URL=http://server:3000
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
command: ["npm", "run", "client:dev"]
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
|
environment:
|
||||||
server:
|
- BASE_URL=http://server:3000
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
networks:
|
networks:
|
||||||
- habitica
|
- habitica
|
||||||
|
ports:
|
||||||
|
- "5173:5173"
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/habitica
|
||||||
|
- /usr/src/habitica/node_modules
|
||||||
|
- /usr/src/habitica/website/client/node_modules
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile-Dev
|
||||||
|
command: ["npm", "start"]
|
||||||
|
depends_on:
|
||||||
|
mongo:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||||
depends_on:
|
|
||||||
- mongo
|
|
||||||
|
|
||||||
mongo:
|
|
||||||
image: mongo:3.6
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
networks:
|
networks:
|
||||||
- habitica
|
- habitica
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/habitica
|
||||||
|
- /usr/src/habitica/node_modules
|
||||||
|
mongo:
|
||||||
|
image: "mongo:7.0"
|
||||||
|
container_name: "habitica-mongodb"
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
hostname: "mongodb"
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
volumes:
|
||||||
|
- "./mongodb-data-docker:/data/db"
|
||||||
|
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs" ]
|
||||||
|
healthcheck:
|
||||||
|
test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet
|
||||||
|
interval: 10s
|
||||||
|
timeout: 30s
|
||||||
|
start_period: 0s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
habitica:
|
habitica:
|
||||||
|
|||||||
+12
-9
@@ -5,7 +5,7 @@ import path from 'path';
|
|||||||
import babel from 'gulp-babel';
|
import babel from 'gulp-babel';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
|
import spawn from 'cross-spawn';
|
||||||
import clean from 'rimraf';
|
import clean from 'rimraf';
|
||||||
|
|
||||||
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
||||||
@@ -35,7 +35,7 @@ gulp.task('build:prod', gulp.series(
|
|||||||
// When used on windows `run-rs` must first be run without the `--keep` option
|
// When used on windows `run-rs` must first be run without the `--keep` option
|
||||||
// in order to be setup correctly, afterwards it can be used.
|
// in order to be setup correctly, afterwards it can be used.
|
||||||
|
|
||||||
const MONGO_PATH = path.join(__dirname, '/../mongodb-data/');
|
const MONGO_PATH = path.join(__dirname, '/../mongodb-data-docker/');
|
||||||
|
|
||||||
gulp.task('build:prepare-mongo', async () => {
|
gulp.task('build:prepare-mongo', async () => {
|
||||||
if (fs.existsSync(MONGO_PATH)) {
|
if (fs.existsSync(MONGO_PATH)) {
|
||||||
@@ -51,29 +51,32 @@ gulp.task('build:prepare-mongo', async () => {
|
|||||||
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
|
||||||
|
|
||||||
// use run-rs without --keep, kill it as soon as the replica set starts
|
// use run-rs without --keep, kill it as soon as the replica set starts
|
||||||
const runRsProcess = spawn('run-rs', ['-v', '4.1.1', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
|
const dockerMongoProcess = spawn('npm', ['run', 'docker:mongo:dev']);
|
||||||
|
|
||||||
for await (const chunk of runRsProcess.stdout) {
|
let manuallyStopped = false;
|
||||||
|
|
||||||
|
for await (const chunk of dockerMongoProcess.stdout) {
|
||||||
const stringChunk = chunk.toString();
|
const stringChunk = chunk.toString();
|
||||||
console.log(stringChunk); // eslint-disable-line no-console
|
console.log(stringChunk); // eslint-disable-line no-console
|
||||||
// kills the process after the replica set is setup
|
// kills the process after the replica set is setup
|
||||||
if (stringChunk.includes('Started replica set')) {
|
if (stringChunk.includes('mongod startup complete')) {
|
||||||
console.log('MongoDB setup correctly.'); // eslint-disable-line no-console
|
console.log('MongoDB setup correctly.'); // eslint-disable-line no-console
|
||||||
runRsProcess.kill();
|
dockerMongoProcess.kill();
|
||||||
|
manuallyStopped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = '';
|
let error = '';
|
||||||
for await (const chunk of runRsProcess.stderr) {
|
for await (const chunk of dockerMongoProcess.stderr) {
|
||||||
const stringChunk = chunk.toString();
|
const stringChunk = chunk.toString();
|
||||||
error += stringChunk;
|
error += stringChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exitCode = await new Promise(resolve => {
|
const exitCode = await new Promise(resolve => {
|
||||||
runRsProcess.on('close', resolve);
|
dockerMongoProcess.on('close', resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exitCode || error.length > 0) {
|
if (!manuallyStopped && (exitCode || error.length > 0)) {
|
||||||
// remove any leftover files
|
// remove any leftover files
|
||||||
clean.sync(MONGO_PATH);
|
clean.sync(MONGO_PATH);
|
||||||
|
|
||||||
|
|||||||
+61
-26
@@ -6,9 +6,21 @@ gulp.task('cache:content', done => {
|
|||||||
// Requiring at runtime because these files access `common`
|
// Requiring at runtime because these files access `common`
|
||||||
// code which in production works only if transpiled so after
|
// code which in production works only if transpiled so after
|
||||||
// gulp build:babel:common has run
|
// gulp build:babel:common has run
|
||||||
const { CONTENT_CACHE_PATH, getLocalizedContentResponse } = require('../website/server/libs/content'); // eslint-disable-line global-require
|
const {
|
||||||
|
CONTENT_CACHE_PATH,
|
||||||
|
getLocalizedContentResponse,
|
||||||
|
IOS_FILTER,
|
||||||
|
ANDROID_FILTER,
|
||||||
|
buildFilterObject,
|
||||||
|
hashForFilter,
|
||||||
|
} = require('../website/server/libs/content'); // eslint-disable-line global-require
|
||||||
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
||||||
|
|
||||||
|
const iosHash = hashForFilter(IOS_FILTER);
|
||||||
|
const iosFilterObj = buildFilterObject(IOS_FILTER);
|
||||||
|
const androidHash = hashForFilter(ANDROID_FILTER);
|
||||||
|
const androidFilterObj = buildFilterObject(ANDROID_FILTER);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// create the cache folder (if it doesn't exist)
|
// create the cache folder (if it doesn't exist)
|
||||||
try {
|
try {
|
||||||
@@ -26,33 +38,56 @@ gulp.task('cache:content', done => {
|
|||||||
getLocalizedContentResponse(langCode),
|
getLocalizedContentResponse(langCode),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
});
|
|
||||||
done();
|
|
||||||
} catch (err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('cache:i18n', done => {
|
|
||||||
// Requiring at runtime because these files access `common`
|
|
||||||
// code which in production works only if transpiled so after
|
|
||||||
// gulp build:babel:common has run
|
|
||||||
const { BROWSER_SCRIPT_CACHE_PATH, geti18nBrowserScript } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
|
||||||
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
|
||||||
|
|
||||||
try {
|
|
||||||
// create the cache folder (if it doesn't exist)
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(BROWSER_SCRIPT_CACHE_PATH);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code !== 'EEXIST') throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and save the i18n browser script for each language
|
|
||||||
langCodes.forEach(languageCode => {
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${BROWSER_SCRIPT_CACHE_PATH}${languageCode}.js`,
|
`${CONTENT_CACHE_PATH}${langCode}${iosHash}.json`,
|
||||||
geti18nBrowserScript(languageCode),
|
getLocalizedContentResponse(langCode, iosFilterObj),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${CONTENT_CACHE_PATH}${langCode}${androidHash}.json`,
|
||||||
|
getLocalizedContentResponse(langCode, androidFilterObj),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
} catch (err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function safeMkdir (path) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'EEXIST') throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('cache:i18n', done => {
|
||||||
|
// Requiring at runtime because these files access `common`
|
||||||
|
// code which in production works only if transpiled so after
|
||||||
|
// gulp build:babel:common has run
|
||||||
|
const { BROWSER_SCRIPT_CACHE_PATH, geti18nCoreBrowserScript, geti18nContentBrowserScript } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
||||||
|
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
|
||||||
|
|
||||||
|
try {
|
||||||
|
// create the cache folders (if they doesn't exist)
|
||||||
|
safeMkdir(BROWSER_SCRIPT_CACHE_PATH);
|
||||||
|
safeMkdir(`${BROWSER_SCRIPT_CACHE_PATH}core/`);
|
||||||
|
safeMkdir(`${BROWSER_SCRIPT_CACHE_PATH}content/`);
|
||||||
|
|
||||||
|
// create and save the i18n browser script for each language
|
||||||
|
langCodes.forEach(languageCode => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${BROWSER_SCRIPT_CACHE_PATH}core/${languageCode}.js`,
|
||||||
|
geti18nCoreBrowserScript(languageCode),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${BROWSER_SCRIPT_CACHE_PATH}content/${languageCode}.js`,
|
||||||
|
geti18nContentBrowserScript(languageCode),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import gulp from 'gulp';
|
|
||||||
import nodemon from 'gulp-nodemon';
|
|
||||||
|
|
||||||
import pkg from '../package.json';
|
|
||||||
|
|
||||||
gulp.task('nodemon', done => {
|
|
||||||
nodemon({
|
|
||||||
script: pkg.main,
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
+5
-6
@@ -49,16 +49,15 @@ function integrationTestCommand (testDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Test task definitions */
|
/* Test task definitions */
|
||||||
gulp.task('test:nodemon', gulp.series(done => {
|
|
||||||
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
|
||||||
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
|
||||||
done();
|
|
||||||
}, 'nodemon'));
|
|
||||||
|
|
||||||
gulp.task('test:prepare:mongo', cb => {
|
gulp.task('test:prepare:mongo', cb => {
|
||||||
const mongooseOptions = getDefaultConnectionOptions();
|
const mongooseOptions = getDefaultConnectionOptions();
|
||||||
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
|
||||||
|
|
||||||
|
console.info({
|
||||||
|
mongooseOptions,
|
||||||
|
connectionUrl,
|
||||||
|
});
|
||||||
|
|
||||||
mongoose.connect(connectionUrl, mongooseOptions)
|
mongoose.connect(connectionUrl, mongooseOptions)
|
||||||
.then(() => mongoose.connection.dropDatabase())
|
.then(() => mongoose.connection.dropDatabase())
|
||||||
.then(() => mongoose.connection.close()).then(() => {
|
.then(() => mongoose.connection.close()).then(() => {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-e
|
|||||||
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-console'); // eslint-disable-line global-require
|
require('./gulp/gulp-console'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-sprites'); // eslint-disable-line global-require
|
require('./gulp/gulp-sprites'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-start'); // eslint-disable-line global-require
|
|
||||||
require('./gulp/gulp-tests'); // eslint-disable-line global-require
|
require('./gulp/gulp-tests'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-transifex-test'); // eslint-disable-line global-require
|
require('./gulp/gulp-transifex-test'); // eslint-disable-line global-require
|
||||||
require('gulp').task('default', gulp.series('test')); // eslint-disable-line global-require
|
require('gulp').task('default', gulp.series('test')); // eslint-disable-line global-require
|
||||||
|
|||||||
+1
-1
Submodule habitica-images updated: 1c6f7d65d7...b7367f328a
@@ -37,7 +37,7 @@ let consoleStamp = require('console-stamp');
|
|||||||
consoleStamp(console);
|
consoleStamp(console);
|
||||||
|
|
||||||
// Initialize configuration
|
// Initialize configuration
|
||||||
require('../../website/server/libs/api-v3/setupNconf')();
|
require('../../website/server/libs/api-v3/setupNconf').default();
|
||||||
|
|
||||||
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
||||||
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ let moment = require('moment');
|
|||||||
consoleStamp(console);
|
consoleStamp(console);
|
||||||
|
|
||||||
// Initialize configuration
|
// Initialize configuration
|
||||||
require('../../website/server/libs/api-v3/setupNconf')();
|
require('../../website/server/libs/api-v3/setupNconf').default();
|
||||||
|
|
||||||
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
let MONGODB_OLD = nconf.get('MONGODB_OLD');
|
||||||
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
let MONGODB_NEW = nconf.get('MONGODB_NEW');
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ require('@babel/register'); // eslint-disable-line import/no-extraneous-dependen
|
|||||||
function setUpServer () {
|
function setUpServer () {
|
||||||
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
||||||
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
||||||
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
|
const setupNconf = require('../website/server/libs/setupNconf').default; // eslint-disable-line global-require
|
||||||
|
|
||||||
setupNconf();
|
setupNconf();
|
||||||
|
|
||||||
// We require src/server and npt src/index because
|
// We require src/server and not src/index because
|
||||||
// 1. nconf is already setup
|
// 1. nconf is already setup
|
||||||
// 2. we don't need clustering
|
// 2. we don't need clustering
|
||||||
require('../website/server/server'); // eslint-disable-line global-require
|
require('../website/server/server'); // eslint-disable-line global-require
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const progressCount = 1000;
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Award users every extant pet and mount
|
* Award every extant piece of equippable gear
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function updateUser (user) {
|
async function updateUser (user) {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { v4 as uuid } from 'uuid';
|
|||||||
|
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
const MIGRATION_NAME = '20181203_take_this';
|
const MIGRATION_NAME = 'YYYYMMDD_take_this';
|
||||||
|
const CHALLENGE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
|
||||||
|
|
||||||
const progressCount = 1000;
|
const progressCount = 1000;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@@ -41,15 +42,15 @@ async function updateUser (user) {
|
|||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
if (push) {
|
if (push) {
|
||||||
return User.update({ _id: user._id }, { $set: set, $push: push }).exec();
|
return User.updateOne({ _id: user._id }, { $set: set, $push: push }).exec();
|
||||||
}
|
}
|
||||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
return User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
challenges: '00708425-d477-41a5-bf27-6270466e7976',
|
challenges: CHALLENGE_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
@@ -72,7 +73,7 @@ export default async function processUsers () {
|
|||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
query._id = {
|
query._id = {
|
||||||
$gt: users[users.length - 1],
|
$gt: users[users.length - 1]._id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+668
-1192
File diff suppressed because it is too large
Load Diff
+21
-20
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "5.35.4",
|
"version": "5.48.0",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.8.1",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.1.1",
|
||||||
"coupon-code": "^0.4.5",
|
"coupon-code": "^0.4.5",
|
||||||
"csv-stringify": "^5.6.5",
|
"csv-stringify": "^5.6.5",
|
||||||
"cwait": "^1.1.1",
|
"cwait": "^1.1.1",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
|
"express-sitemap-xml": "^3.1.0",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "^5.2.0",
|
||||||
"firebase-admin": "^12.1.1",
|
"firebase-admin": "^12.1.1",
|
||||||
"glob": "^8.1.0",
|
"glob": "^8.1.0",
|
||||||
@@ -38,9 +39,9 @@
|
|||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
"gulp-filter": "^7.0.0",
|
"gulp-filter": "^7.0.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-nodemon": "^2.5.0",
|
|
||||||
"gulp.spritesmith": "^6.13.0",
|
"gulp.spritesmith": "^6.13.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^4.1.0",
|
||||||
|
"heapdump": "^0.3.15",
|
||||||
"helmet": "^4.6.0",
|
"helmet": "^4.6.0",
|
||||||
"in-app-purchase": "^1.11.3",
|
"in-app-purchase": "^1.11.3",
|
||||||
"js2xmlparser": "^5.0.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
@@ -49,14 +50,15 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"merge-stream": "^2.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
"method-override": "^3.0.0",
|
"method-override": "^3.0.0",
|
||||||
|
"micromustache": "^8.0.3",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||||
"mongoose": "^8.9.5",
|
"mongoose": "^8.23.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.1",
|
||||||
|
"nan": "^2.25.0",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"node-gcm": "^1.0.5",
|
"node-gcm": "^1.0.5",
|
||||||
"nodemon": "^2.0.20",
|
"on-headers": "^1.1.0",
|
||||||
"on-headers": "^1.0.2",
|
|
||||||
"passport": "^0.5.3",
|
"passport": "^0.5.3",
|
||||||
"passport-facebook": "^3.0.0",
|
"passport-facebook": "^3.0.0",
|
||||||
"passport-google-oauth2": "^0.2.0",
|
"passport-google-oauth2": "^0.2.0",
|
||||||
@@ -72,11 +74,9 @@
|
|||||||
"sinon": "^15.2.0",
|
"sinon": "^15.2.0",
|
||||||
"stripe": "^12.18.0",
|
"stripe": "^12.18.0",
|
||||||
"superagent": "^8.1.2",
|
"superagent": "^8.1.2",
|
||||||
"universal-analytics": "^0.5.3",
|
|
||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
|
||||||
"winston": "^3.10.0",
|
"winston": "^3.10.0",
|
||||||
"winston-loggly-bulk": "^3.3.0",
|
"winston-loggly-bulk": "^3.3.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
@@ -100,23 +100,25 @@
|
|||||||
"test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
|
"test:sanity": "nyc --silent --no-clean mocha test/sanity --recursive",
|
||||||
"test:common": "nyc --silent --no-clean mocha test/common --recursive",
|
"test:common": "nyc --silent --no-clean mocha test/common --recursive",
|
||||||
"test:content": "nyc --silent --no-clean mocha test/content --recursive",
|
"test:content": "nyc --silent --no-clean mocha test/content --recursive",
|
||||||
"test:nodemon": "gulp test:nodemon",
|
|
||||||
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
|
"coverage": "nyc report --reporter=html --report-dir coverage/results; open coverage/results/index.html",
|
||||||
"sprites": "gulp sprites:compile",
|
"sprites": "gulp sprites:compile",
|
||||||
"client:dev": "cd website/client && npm run serve",
|
"client:dev": "cd website/client && npm run serve",
|
||||||
|
"client:dev:docker": "cd website/client && npm run serve:docker",
|
||||||
"client:build": "cd website/client && npm run build",
|
"client:build": "cd website/client && npm run build",
|
||||||
"client:unit": "cd website/client && npm run test:unit",
|
"client:unit": "cd website/client && npm run test:unit",
|
||||||
"start": "gulp nodemon",
|
"start": "node --watch ./website/server/index.js",
|
||||||
"start:simple": "node ./website/server/index.js",
|
"debug": "node --watch --inspect ./website/server/index.js",
|
||||||
"debug": "gulp nodemon --inspect",
|
"docker:aio": "docker compose -f docker-compose.yml up",
|
||||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
"docker:mongo:dev": "docker compose -f docker-compose.mongo-only.yml up",
|
||||||
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
"docker:mongo:dev:down": "docker compose -f docker-compose.mongo-only.yml down",
|
||||||
|
"docker:mongo:test": "docker compose -f docker-compose.mongo-test-local.yml up",
|
||||||
|
"mongo:test": "node scripts/start-local-mongo.mjs --test-db",
|
||||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||||
"apidoc": "gulp apidoc",
|
"apidoc": "gulp apidoc",
|
||||||
"heroku-postbuild": ".heroku/report_deploy.sh"
|
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.8.2",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-moment": "^0.1.0",
|
"chai-moment": "^0.1.0",
|
||||||
@@ -126,7 +128,6 @@
|
|||||||
"monk": "^7.3.4",
|
"monk": "^7.3.4",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"run-rs": "^0.7.7",
|
|
||||||
"sinon-chai": "^3.7.0",
|
"sinon-chai": "^3.7.0",
|
||||||
"sinon-stub-promise": "^4.0.0"
|
"sinon-stub-promise": "^4.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,15 +71,14 @@ async function deleteHabiticaData (user, email) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function processEmailAddress (email) {
|
async function processEmailAddress (email) {
|
||||||
const emailRegex = new RegExp(`^${email}$`, 'i');
|
|
||||||
const localUsers = await User.find(
|
const localUsers = await User.find(
|
||||||
{ 'auth.local.email': emailRegex },
|
{ 'auth.local.email': email },
|
||||||
{ _id: 1, apiToken: 1, auth: 1 },
|
{ _id: 1, apiToken: 1, auth: 1 },
|
||||||
).exec();
|
).exec();
|
||||||
|
|
||||||
const socialUsers = await User.find(
|
const socialUsers = await User.find(
|
||||||
{
|
{
|
||||||
'auth.local.email': { $not: emailRegex },
|
'auth.local.email': { $ne: email },
|
||||||
$or: [
|
$or: [
|
||||||
{ 'auth.facebook.emails.value': email },
|
{ 'auth.facebook.emails.value': email },
|
||||||
{ 'auth.google.emails.value': email },
|
{ 'auth.google.emails.value': email },
|
||||||
|
|||||||
+11
-6
@@ -8,7 +8,17 @@ const TASK_VALUE_CHANGE_FACTOR = 0.9747;
|
|||||||
const MIN_TASK_VALUE = -47.27;
|
const MIN_TASK_VALUE = -47.27;
|
||||||
|
|
||||||
async function updateTeamTasks (team) {
|
async function updateTeamTasks (team) {
|
||||||
|
if (team.purchased.plan.dateTerminated) {
|
||||||
|
const dateTerminated = new Date(team.purchased.plan.dateTerminated);
|
||||||
|
if (dateTerminated < new Date()) {
|
||||||
|
team.purchased.plan.customerId = undefined;
|
||||||
|
team.markModified('purchased.plan');
|
||||||
|
return team.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toSave = [];
|
const toSave = [];
|
||||||
|
|
||||||
let teamLeader = await User.findOne({ _id: team.leader }, 'preferences').exec();
|
let teamLeader = await User.findOne({ _id: team.leader }, 'preferences').exec();
|
||||||
|
|
||||||
if (!teamLeader) { // why would this happen?
|
if (!teamLeader) { // why would this happen?
|
||||||
@@ -93,12 +103,7 @@ async function updateTeamTasks (team) {
|
|||||||
export default async function processTeamsCron () {
|
export default async function processTeamsCron () {
|
||||||
const activeTeams = await Group.find({
|
const activeTeams = await Group.find({
|
||||||
'purchased.plan.customerId': { $exists: true },
|
'purchased.plan.customerId': { $exists: true },
|
||||||
$or: [
|
}, { cron: 1, leader: 1, purchased: 1 }).exec();
|
||||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
|
||||||
{ 'purchased.plan.dateTerminated': null },
|
|
||||||
{ 'purchased.plan.dateTerminated': { $gt: new Date() } },
|
|
||||||
],
|
|
||||||
}).exec();
|
|
||||||
|
|
||||||
const cronPromises = activeTeams.map(updateTeamTasks);
|
const cronPromises = activeTeams.map(updateTeamTasks);
|
||||||
return Promise.all(cronPromises);
|
return Promise.all(cronPromises);
|
||||||
|
|||||||
@@ -1,595 +0,0 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
import nconf from 'nconf';
|
|
||||||
import Amplitude from 'amplitude';
|
|
||||||
import { Visitor } from 'universal-analytics';
|
|
||||||
import * as analyticsService from '../../../../website/server/libs/analyticsService';
|
|
||||||
|
|
||||||
describe('analyticsService', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
sandbox.stub(Amplitude.prototype, 'track').returns(Promise.resolve());
|
|
||||||
|
|
||||||
sandbox.stub(Visitor.prototype, 'event');
|
|
||||||
sandbox.stub(Visitor.prototype, 'transaction');
|
|
||||||
});
|
|
||||||
|
|
||||||
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(() => {
|
|
||||||
Visitor.prototype.event.yields();
|
|
||||||
|
|
||||||
eventType = 'Cron';
|
|
||||||
data = {
|
|
||||||
category: 'behavior',
|
|
||||||
uuid: 'unique-user-id',
|
|
||||||
resting: true,
|
|
||||||
cronCount: 5,
|
|
||||||
headers: {
|
|
||||||
'x-client': 'habitica-web',
|
|
||||||
'user-agent': '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('GA', () => {
|
|
||||||
it('calls out to GA', () => analyticsService.track(eventType, data)
|
|
||||||
.then(() => {
|
|
||||||
expect(Visitor.prototype.event).to.be.calledOnce;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('sends details about event', () => analyticsService.track(eventType, data)
|
|
||||||
.then(() => {
|
|
||||||
expect(Visitor.prototype.event).to.be.calledWith({
|
|
||||||
ea: 'Cron',
|
|
||||||
ec: 'behavior',
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#trackPurchase', () => {
|
|
||||||
let data; let
|
|
||||||
itemSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
Visitor.prototype.event.yields();
|
|
||||||
|
|
||||||
itemSpy = sandbox.stub().returnsThis();
|
|
||||||
|
|
||||||
Visitor.prototype.transaction.returns({
|
|
||||||
item: itemSpy,
|
|
||||||
send: sandbox.stub().yields(),
|
|
||||||
});
|
|
||||||
|
|
||||||
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': '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
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' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('GA', () => {
|
|
||||||
it('calls out to GA', () => analyticsService.trackPurchase(data)
|
|
||||||
.then(() => {
|
|
||||||
expect(Visitor.prototype.event).to.be.calledOnce;
|
|
||||||
expect(Visitor.prototype.transaction).to.be.calledOnce;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('sends details about purchase', () => analyticsService.trackPurchase(data)
|
|
||||||
.then(() => {
|
|
||||||
expect(Visitor.prototype.event).to.be.calledWith({
|
|
||||||
ea: 'checkout',
|
|
||||||
ec: 'commerce',
|
|
||||||
el: 'PayPal',
|
|
||||||
ev: 8,
|
|
||||||
});
|
|
||||||
expect(Visitor.prototype.transaction).to.be.calledWith('user-id', 8);
|
|
||||||
expect(itemSpy).to.be.calledWith(8, 1, 'paypal-checkout', 'Gems', 'checkout');
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mockAnalyticsService', () => {
|
|
||||||
it('has stubbed track method', () => {
|
|
||||||
expect(analyticsService.mockAnalyticsService).to.respondTo('track');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has stubbed trackPurchase method', () => {
|
|
||||||
expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -34,6 +34,7 @@ describe('bug-report', () => {
|
|||||||
emailData: {
|
emailData: {
|
||||||
BROWSER_UA: userAgent,
|
BROWSER_UA: userAgent,
|
||||||
REPORT_MSG: userMessage,
|
REPORT_MSG: userMessage,
|
||||||
|
USER_ANALYTICS: undefined,
|
||||||
USER_CLASS: 'warrior',
|
USER_CLASS: 'warrior',
|
||||||
USER_CONSECUTIVE_MONTHS: 0,
|
USER_CONSECUTIVE_MONTHS: 0,
|
||||||
USER_COSTUME: 'false',
|
USER_COSTUME: 'false',
|
||||||
|
|||||||
+116
-136
@@ -13,7 +13,6 @@ import { cron, cronWrapper } from '../../../../website/server/libs/cron';
|
|||||||
import { model as User } from '../../../../website/server/models/user';
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
import * as Tasks from '../../../../website/server/models/task';
|
import * as Tasks from '../../../../website/server/models/task';
|
||||||
import common from '../../../../website/common';
|
import common from '../../../../website/common';
|
||||||
import * as analytics from '../../../../website/server/libs/analyticsService';
|
|
||||||
import { model as Group } from '../../../../website/server/models/group';
|
import { model as Group } from '../../../../website/server/models/group';
|
||||||
|
|
||||||
const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime();
|
const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime();
|
||||||
@@ -41,20 +40,17 @@ describe('cron', async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
sinon.spy(analytics, 'track');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (clock !== null) clock.restore();
|
if (clock !== null) clock.restore();
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates user.preferences.timezoneOffsetAtLastCron', async () => {
|
it('updates user.preferences.timezoneOffsetAtLastCron', async () => {
|
||||||
const timezoneUtcOffsetFromUserPrefs = -1;
|
const timezoneUtcOffsetFromUserPrefs = -1;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs,
|
user, tasksByType, daysMissed, timezoneUtcOffsetFromUserPrefs,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
|
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
|
||||||
@@ -63,7 +59,7 @@ describe('cron', async () => {
|
|||||||
it('resets user.items.lastDrop.count', async () => {
|
it('resets user.items.lastDrop.count', async () => {
|
||||||
user.items.lastDrop.count = 4;
|
user.items.lastDrop.count = 4;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.items.lastDrop.count).to.equal(0);
|
expect(user.items.lastDrop.count).to.equal(0);
|
||||||
});
|
});
|
||||||
@@ -71,26 +67,11 @@ describe('cron', async () => {
|
|||||||
it('increments user cron count', async () => {
|
it('increments user cron count', async () => {
|
||||||
const cronCountBefore = user.flags.cronCount;
|
const cronCountBefore = user.flags.cronCount;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore);
|
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 () => {
|
describe('end of the month perks', async () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user.purchased.plan.customerId = 'subscribedId';
|
user.purchased.plan.customerId = 'subscribedId';
|
||||||
@@ -101,7 +82,7 @@ describe('cron', async () => {
|
|||||||
user.purchased.plan.dateUpdated = new Date('2018-12-11');
|
user.purchased.plan.dateUpdated = new Date('2018-12-11');
|
||||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
|
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
|
||||||
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
|
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');
|
user.purchased.plan.dateUpdated = new Date('2018-11-11');
|
||||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
|
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
|
||||||
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
|
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 () => {
|
it('resets plan.gemsBought on a new month', async () => {
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||||
});
|
});
|
||||||
@@ -131,7 +112,7 @@ describe('cron', async () => {
|
|||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
user.purchased.plan.dateUpdated = undefined;
|
user.purchased.plan.dateUpdated = undefined;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||||
});
|
});
|
||||||
@@ -142,7 +123,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||||
});
|
});
|
||||||
@@ -150,7 +131,7 @@ describe('cron', async () => {
|
|||||||
it('resets plan.dateUpdated on a new month', async () => {
|
it('resets plan.dateUpdated on a new month', async () => {
|
||||||
const currentMonth = moment().startOf('month');
|
const currentMonth = moment().startOf('month');
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(moment(user.purchased.plan.dateUpdated).startOf('month').isSame(currentMonth)).to.eql(true);
|
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 () => {
|
it('increments plan.consecutive.count', async () => {
|
||||||
user.purchased.plan.consecutive.count = 0;
|
user.purchased.plan.consecutive.count = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.count).to.equal(1);
|
expect(user.purchased.plan.consecutive.count).to.equal(1);
|
||||||
});
|
});
|
||||||
@@ -166,7 +147,7 @@ describe('cron', async () => {
|
|||||||
it('increments plan.cumulativeCount', async () => {
|
it('increments plan.cumulativeCount', async () => {
|
||||||
user.purchased.plan.cumulativeCount = 0;
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.cumulativeCount).to.equal(1);
|
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.dateUpdated = moment().subtract(2, 'months').toDate();
|
||||||
user.purchased.plan.consecutive.count = 0;
|
user.purchased.plan.consecutive.count = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.count).to.equal(2);
|
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.dateUpdated = moment().subtract(3, 'months').toDate();
|
||||||
user.purchased.plan.cumulativeCount = 0;
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.cumulativeCount).to.equal(3);
|
expect(user.purchased.plan.cumulativeCount).to.equal(3);
|
||||||
});
|
});
|
||||||
@@ -196,7 +177,7 @@ describe('cron', async () => {
|
|||||||
user.purchased.plan.consecutive.trinkets = 1;
|
user.purchased.plan.consecutive.trinkets = 1;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
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.gemCapExtra = 26;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
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 () => {
|
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 });
|
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.customerId).to.exist;
|
expect(user.purchased.plan.customerId).to.exist;
|
||||||
});
|
});
|
||||||
@@ -225,7 +206,7 @@ describe('cron', async () => {
|
|||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.purchased.plan.customerId).to.not.exist;
|
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
|
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
||||||
// e.g., from time zone oddness.
|
// e.g., from time zone oddness.
|
||||||
await cron({
|
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.count).to.equal(1);
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
@@ -276,7 +257,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(10);
|
||||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
@@ -311,7 +292,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(1);
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
@@ -323,7 +304,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(10);
|
||||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
@@ -358,7 +339,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(1);
|
||||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
@@ -391,7 +372,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(1);
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
@@ -403,7 +384,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
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.count).to.equal(10);
|
||||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||||
@@ -439,7 +420,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
await cron({
|
||||||
user: user3g, tasksByType, daysMissed, analytics,
|
user: user3g, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
||||||
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
|
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
|
||||||
@@ -452,7 +433,7 @@ describe('cron', async () => {
|
|||||||
.add(2, 'days')
|
.add(2, 'days')
|
||||||
.toDate());
|
.toDate());
|
||||||
await cron({
|
await cron({
|
||||||
user: user3g, tasksByType, daysMissed, analytics,
|
user: user3g, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
// subscription has been erased by now
|
// subscription has been erased by now
|
||||||
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
|
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 () => {
|
it('resets plan.gemsBought on a new month', async () => {
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||||
});
|
});
|
||||||
@@ -482,14 +463,14 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not reset plan.dateUpdated on a new month', async () => {
|
it('does not reset plan.dateUpdated on a new month', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.dateUpdated).to.be.empty;
|
expect(user.purchased.plan.dateUpdated).to.be.empty;
|
||||||
});
|
});
|
||||||
@@ -497,7 +478,7 @@ describe('cron', async () => {
|
|||||||
it('does not increment plan.consecutive.count', async () => {
|
it('does not increment plan.consecutive.count', async () => {
|
||||||
user.purchased.plan.consecutive.count = 0;
|
user.purchased.plan.consecutive.count = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||||
});
|
});
|
||||||
@@ -505,7 +486,7 @@ describe('cron', async () => {
|
|||||||
it('does not increment plan.cumulativeCount', async () => {
|
it('does not increment plan.cumulativeCount', async () => {
|
||||||
user.purchased.plan.cumulativeCount = 0;
|
user.purchased.plan.cumulativeCount = 0;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.cumulativeCount).to.equal(0);
|
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 () => {
|
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;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(0);
|
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 () => {
|
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;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
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.gemCapExtra = 26;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
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 () => {
|
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 });
|
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.purchased.plan.customerId).to.not.exist;
|
expect(user.purchased.plan.customerId).to.not.exist;
|
||||||
});
|
});
|
||||||
@@ -564,7 +545,7 @@ describe('cron', async () => {
|
|||||||
it('should make uncompleted todos redder', async () => {
|
it('should make uncompleted todos redder', async () => {
|
||||||
const valueBefore = tasksByType.todos[0].value;
|
const valueBefore = tasksByType.todos[0].value;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore);
|
expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore);
|
||||||
});
|
});
|
||||||
@@ -573,7 +554,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.todos[0].completed = true;
|
tasksByType.todos[0].completed = true;
|
||||||
const valueBefore = tasksByType.todos[0].value;
|
const valueBefore = tasksByType.todos[0].value;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.todos[0].value).to.equal(valueBefore);
|
expect(tasksByType.todos[0].value).to.equal(valueBefore);
|
||||||
});
|
});
|
||||||
@@ -582,7 +563,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.todos[0].completed = true;
|
tasksByType.todos[0].completed = true;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.history.todos).to.be.lengthOf(1);
|
expect(user.history.todos).to.be.lengthOf(1);
|
||||||
@@ -608,7 +589,7 @@ describe('cron', async () => {
|
|||||||
expect(user.tasksOrder.todos).to.be.lengthOf(3);
|
expect(user.tasksOrder.todos).to.be.lengthOf(3);
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
|
// 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
|
const original = user.tasksOrder.todos; // Preserve the original order
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
let listsAreEqual = true;
|
let listsAreEqual = true;
|
||||||
@@ -675,7 +656,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].everyX = 5;
|
tasksByType.dailys[0].everyX = 5;
|
||||||
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||||
});
|
});
|
||||||
@@ -686,7 +667,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].everyX = 5;
|
tasksByType.dailys[0].everyX = 5;
|
||||||
tasksByType.dailys[0].startDate = moment().toDate();
|
tasksByType.dailys[0].startDate = moment().toDate();
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].isDue).to.exist;
|
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||||
});
|
});
|
||||||
@@ -696,14 +677,14 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].everyX = 5;
|
tasksByType.dailys[0].everyX = 5;
|
||||||
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].nextDue.length).to.eql(6);
|
expect(tasksByType.dailys[0].nextDue.length).to.eql(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add history', async () => {
|
it('should add history', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
|
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
|
||||||
});
|
});
|
||||||
@@ -711,7 +692,7 @@ describe('cron', async () => {
|
|||||||
it('should set tasks completed to false', async () => {
|
it('should set tasks completed to false', async () => {
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||||
});
|
});
|
||||||
@@ -720,7 +701,7 @@ describe('cron', async () => {
|
|||||||
user.preferences.sleep = true;
|
user.preferences.sleep = true;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
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].checklist.push({ title: 'test', completed: false });
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
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].checklist.push({ title: 'test', completed: false });
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
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].checklist.push({ title: 'test', completed: false });
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||||
});
|
});
|
||||||
@@ -759,7 +740,7 @@ describe('cron', async () => {
|
|||||||
const hpBefore = user.stats.hp;
|
const hpBefore = user.stats.hp;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||||
});
|
});
|
||||||
@@ -770,7 +751,7 @@ describe('cron', async () => {
|
|||||||
const hpBefore = user.stats.hp;
|
const hpBefore = user.stats.hp;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.stats.hp).to.equal(hpBefore);
|
expect(user.stats.hp).to.equal(hpBefore);
|
||||||
});
|
});
|
||||||
@@ -784,7 +765,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
|
|
||||||
cronOverride({
|
cronOverride({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.hp).to.equal(hpBefore);
|
expect(user.stats.hp).to.equal(hpBefore);
|
||||||
@@ -797,7 +778,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.hp).to.equal(hpBefore);
|
expect(user.stats.hp).to.equal(hpBefore);
|
||||||
@@ -808,7 +789,7 @@ describe('cron', async () => {
|
|||||||
let hpBefore = user.stats.hp;
|
let hpBefore = user.stats.hp;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
const hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp;
|
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: 'test', completed: true });
|
||||||
tasksByType.dailys[0].checklist.push({ title: 'test2', completed: false });
|
tasksByType.dailys[0].checklist.push({ title: 'test2', completed: false });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
const hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp;
|
const hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp;
|
||||||
|
|
||||||
@@ -829,7 +810,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
|
|
||||||
const progress = await cron({
|
const progress = await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(progress.down).to.equal(-1);
|
expect(progress.down).to.equal(-1);
|
||||||
@@ -841,7 +822,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||||
|
|
||||||
const progress = await cron({
|
const progress = await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(progress.down).to.equal(0);
|
expect(progress.down).to.equal(0);
|
||||||
@@ -862,7 +843,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[1].frequency = 'daily';
|
tasksByType.dailys[1].frequency = 'daily';
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.hp).to.equal(48);
|
expect(user.stats.hp).to.equal(48);
|
||||||
@@ -886,7 +867,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.habits[0].down = false;
|
tasksByType.habits[0].down = false;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
||||||
@@ -897,7 +878,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.habits[0].up = false;
|
tasksByType.habits[0].up = false;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
||||||
@@ -909,7 +890,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.habits[0].down = true;
|
tasksByType.habits[0].down = true;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].value).to.equal(1);
|
expect(tasksByType.habits[0].value).to.equal(1);
|
||||||
@@ -928,7 +909,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.habits[0].counterDown = 1;
|
tasksByType.habits[0].counterDown = 1;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -941,7 +922,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.habits[0].counterDown = 1;
|
tasksByType.habits[0].counterDown = 1;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -955,7 +936,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -964,7 +945,7 @@ describe('cron', async () => {
|
|||||||
// should reset
|
// should reset
|
||||||
daysMissed = 8;
|
daysMissed = 8;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -988,7 +969,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -1002,7 +983,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should reset after user CDS
|
// should reset after user CDS
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1026,7 +1007,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -1036,7 +1017,7 @@ describe('cron', async () => {
|
|||||||
// should reset
|
// should reset
|
||||||
daysMissed = 2;
|
daysMissed = 2;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1060,7 +1041,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should reset
|
// should reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1084,7 +1065,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -1098,7 +1079,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -1107,7 +1088,7 @@ describe('cron', async () => {
|
|||||||
// should reset
|
// should reset
|
||||||
daysMissed = 32;
|
daysMissed = 32;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1132,7 +1113,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should reset
|
// should reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1156,7 +1137,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
// should not reset
|
// should not reset
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||||
@@ -1166,7 +1147,7 @@ describe('cron', async () => {
|
|||||||
// should reset
|
// should reset
|
||||||
daysMissed = 2;
|
daysMissed = 2;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||||
@@ -1199,7 +1180,7 @@ describe('cron', async () => {
|
|||||||
user.stats.lvl = 2;
|
user.stats.lvl = 2;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.history.exp).to.have.lengthOf(1);
|
expect(user.history.exp).to.have.lengthOf(1);
|
||||||
@@ -1212,7 +1193,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].isDue = true;
|
tasksByType.dailys[0].isDue = true;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.achievements.perfect).to.equal(1);
|
expect(user.achievements.perfect).to.equal(1);
|
||||||
@@ -1224,7 +1205,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].isDue = false;
|
tasksByType.dailys[0].isDue = false;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.achievements.perfect).to.equal(0);
|
expect(user.achievements.perfect).to.equal(0);
|
||||||
@@ -1238,7 +1219,7 @@ describe('cron', async () => {
|
|||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
@@ -1256,7 +1237,7 @@ describe('cron', async () => {
|
|||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
@@ -1280,7 +1261,7 @@ describe('cron', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.equal(0);
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
@@ -1307,7 +1288,7 @@ describe('cron', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.equal(0);
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
@@ -1333,7 +1314,7 @@ describe('cron', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.equal(0);
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
@@ -1360,7 +1341,7 @@ describe('cron', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.equal(0);
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
@@ -1381,7 +1362,7 @@ describe('cron', async () => {
|
|||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
cronOverride({
|
cronOverride({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
@@ -1401,7 +1382,7 @@ describe('cron', async () => {
|
|||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
cronOverride({
|
cronOverride({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
@@ -1420,7 +1401,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.stats.mp).to.be.greaterThan(mpBefore);
|
expect(user.stats.mp).to.be.greaterThan(mpBefore);
|
||||||
|
|
||||||
@@ -1436,7 +1417,7 @@ describe('cron', async () => {
|
|||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.stats.mp).to.equal(mpBefore);
|
expect(user.stats.mp).to.equal(mpBefore);
|
||||||
|
|
||||||
@@ -1449,7 +1430,7 @@ describe('cron', async () => {
|
|||||||
user.stats.mp = 120;
|
user.stats.mp = 120;
|
||||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.stats.mp).to.equal(common.statsComputed(user).maxMP);
|
expect(user.stats.mp).to.equal(common.statsComputed(user).maxMP);
|
||||||
|
|
||||||
@@ -1482,7 +1463,7 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
it('resets user progress', async () => {
|
it('resets user progress', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.party.quest.progress.up).to.equal(0);
|
expect(user.party.quest.progress.up).to.equal(0);
|
||||||
expect(user.party.quest.progress.down).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 () => {
|
it('applies the user progress', async () => {
|
||||||
const progress = await cron({
|
const progress = await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(progress.down).to.equal(-1);
|
expect(progress.down).to.equal(-1);
|
||||||
});
|
});
|
||||||
@@ -1529,19 +1510,19 @@ describe('cron', async () => {
|
|||||||
describe('login incentives', async () => {
|
describe('login incentives', async () => {
|
||||||
it('increments incentive counter each cron', async () => {
|
it('increments incentive counter each cron', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 1 });
|
user.lastCron = moment(new Date()).subtract({ days: 1 });
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(2);
|
expect(user.loginIncentives).to.eql(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pushes a notification of the day\'s incentive each cron', async () => {
|
it('pushes a notification of the day\'s incentive each cron', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.notifications.length).to.eql(1);
|
expect(user.notifications.length).to.eql(1);
|
||||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||||
@@ -1549,13 +1530,13 @@ describe('cron', async () => {
|
|||||||
|
|
||||||
it('replaces previous notifications', async () => {
|
it('replaces previous notifications', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE');
|
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 () => {
|
it('increments loginIncentives by 1 even if days are skipped in between', async () => {
|
||||||
daysMissed = 3;
|
daysMissed = 3;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
});
|
});
|
||||||
@@ -1574,14 +1555,14 @@ describe('cron', async () => {
|
|||||||
it('increments loginIncentives by 1 even if user is sleeping', async () => {
|
it('increments loginIncentives by 1 even if user is sleeping', async () => {
|
||||||
user.preferences.sleep = true;
|
user.preferences.sleep = true;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('awards user bard robes if login incentive is 1', async () => {
|
it('awards user bard robes if login incentive is 1', async () => {
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true);
|
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 () => {
|
it('awards user incentive backgrounds if login incentive is 2', async () => {
|
||||||
user.loginIncentives = 1;
|
user.loginIncentives = 1;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(2);
|
expect(user.loginIncentives).to.eql(2);
|
||||||
expect(user.purchased.background.blue).to.eql(true);
|
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 () => {
|
it('awards user Bard Hat if login incentive is 3', async () => {
|
||||||
user.loginIncentives = 2;
|
user.loginIncentives = 2;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(3);
|
expect(user.loginIncentives).to.eql(3);
|
||||||
expect(user.items.gear.owned.head_special_bardHat).to.eql(true);
|
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 () => {
|
it('awards user RoyalPurple Hatching Potion if login incentive is 4', async () => {
|
||||||
user.loginIncentives = 3;
|
user.loginIncentives = 3;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(4);
|
expect(user.loginIncentives).to.eql(4);
|
||||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
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 () => {
|
it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', async () => {
|
||||||
user.loginIncentives = 4;
|
user.loginIncentives = 4;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(5);
|
expect(user.loginIncentives).to.eql(5);
|
||||||
|
|
||||||
@@ -1639,7 +1620,7 @@ describe('cron', async () => {
|
|||||||
it('awards user moon quest if login incentive is 7', async () => {
|
it('awards user moon quest if login incentive is 7', async () => {
|
||||||
user.loginIncentives = 6;
|
user.loginIncentives = 6;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(7);
|
expect(user.loginIncentives).to.eql(7);
|
||||||
expect(user.items.quests.moon1).to.eql(1);
|
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 () => {
|
it('awards user RoyalPurple Hatching Potion if login incentive is 10', async () => {
|
||||||
user.loginIncentives = 9;
|
user.loginIncentives = 9;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(10);
|
expect(user.loginIncentives).to.eql(10);
|
||||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
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 () => {
|
it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', async () => {
|
||||||
user.loginIncentives = 13;
|
user.loginIncentives = 13;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(14);
|
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 () => {
|
it('awards user a bard instrument if login incentive is 18', async () => {
|
||||||
user.loginIncentives = 17;
|
user.loginIncentives = 17;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(18);
|
expect(user.loginIncentives).to.eql(18);
|
||||||
expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true);
|
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 () => {
|
it('awards user second moon quest if login incentive is 22', async () => {
|
||||||
user.loginIncentives = 21;
|
user.loginIncentives = 21;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(22);
|
expect(user.loginIncentives).to.eql(22);
|
||||||
expect(user.items.quests.moon2).to.eql(1);
|
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 () => {
|
it('awards user a RoyalPurple hatching potion if login incentive is 26', async () => {
|
||||||
user.loginIncentives = 25;
|
user.loginIncentives = 25;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(26);
|
expect(user.loginIncentives).to.eql(26);
|
||||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
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 () => {
|
it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', async () => {
|
||||||
user.loginIncentives = 29;
|
user.loginIncentives = 29;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(30);
|
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 () => {
|
it('awards user a RoyalPurple hatching potion if login incentive is 35', async () => {
|
||||||
user.loginIncentives = 34;
|
user.loginIncentives = 34;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(35);
|
expect(user.loginIncentives).to.eql(35);
|
||||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
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 () => {
|
it('awards user the third moon quest if login incentive is 40', async () => {
|
||||||
user.loginIncentives = 39;
|
user.loginIncentives = 39;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(40);
|
expect(user.loginIncentives).to.eql(40);
|
||||||
expect(user.items.quests.moon3).to.eql(1);
|
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 () => {
|
it('awards user a RoyalPurple hatching potion if login incentive is 45', async () => {
|
||||||
user.loginIncentives = 44;
|
user.loginIncentives = 44;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(45);
|
expect(user.loginIncentives).to.eql(45);
|
||||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
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 () => {
|
it('awards user a saddle if login incentive is 50', async () => {
|
||||||
user.loginIncentives = 49;
|
user.loginIncentives = 49;
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed,
|
||||||
});
|
});
|
||||||
expect(user.loginIncentives).to.eql(50);
|
expect(user.loginIncentives).to.eql(50);
|
||||||
expect(user.items.food.Saddle).to.eql(1);
|
expect(user.items.food.Saddle).to.eql(1);
|
||||||
@@ -1766,7 +1747,6 @@ describe('cron wrapper', () => {
|
|||||||
res = generateRes();
|
res = generateRes();
|
||||||
req = generateReq();
|
req = generateReq();
|
||||||
user = await res.locals.user.save();
|
user = await res.locals.user.save();
|
||||||
res.analytics = analytics;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ describe('emails', () => {
|
|||||||
|
|
||||||
sendTxn(mailingInfo, emailType);
|
sendTxn(mailingInfo, emailType);
|
||||||
expect(got.post).to.be.called;
|
expect(got.post).to.be.called;
|
||||||
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
|
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
|
||||||
json: {
|
json: {
|
||||||
data: {
|
data: {
|
||||||
emailType: sinon.match.same(emailType),
|
emailType: sinon.match.same(emailType),
|
||||||
@@ -234,7 +234,7 @@ describe('emails', () => {
|
|||||||
|
|
||||||
sendTxn(mailingInfo, emailType);
|
sendTxn(mailingInfo, emailType);
|
||||||
expect(got.post).to.be.called;
|
expect(got.post).to.be.called;
|
||||||
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
|
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
|
||||||
json: {
|
json: {
|
||||||
data: {
|
data: {
|
||||||
emailType: sinon.match.same(emailType),
|
emailType: sinon.match.same(emailType),
|
||||||
@@ -254,7 +254,7 @@ describe('emails', () => {
|
|||||||
|
|
||||||
sendTxn(mailingInfo, emailType, variables);
|
sendTxn(mailingInfo, emailType, variables);
|
||||||
expect(got.post).to.be.called;
|
expect(got.post).to.be.called;
|
||||||
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
|
expect(got.post).to.be.calledWith('http://example.com/job', sinon.match({
|
||||||
json: {
|
json: {
|
||||||
data: {
|
data: {
|
||||||
variables: sinon.match(value => value[0].name === 'BASE_URL', 'matches variables'),
|
variables: sinon.match(value => value[0].name === 'BASE_URL', 'matches variables'),
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ describe('highlightMentions', () => {
|
|||||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('highlights users with case-insensitive matching', async () => {
|
||||||
|
const text = '@USER: message @User2 @USER3';
|
||||||
|
const result = await highlightMentions(text);
|
||||||
|
expect(result[0]).to.equal('[@USER](/profile/111): message [@User2](/profile/222) [@USER3](/profile/333)');
|
||||||
|
});
|
||||||
|
|
||||||
it('doesn\'t highlight nonexisting users', async () => {
|
it('doesn\'t highlight nonexisting users', async () => {
|
||||||
const text = '@nouser message';
|
const text = '@nouser message';
|
||||||
const result = await highlightMentions(text);
|
const result = await highlightMentions(text);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -66,13 +66,15 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
group.purchased.plan.customerId = 'customer-id';
|
group.purchased.plan.customerId = 'customer-id';
|
||||||
group.purchased.plan.planId = subKey;
|
group.purchased.plan.planId = subKey;
|
||||||
group.purchased.plan.lastBillingDate = new Date();
|
group.purchased.plan.lastBillingDate = new Date();
|
||||||
await group.save();
|
await group.save();
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
await user.save();
|
||||||
|
|
||||||
subscriptionBlock = common.content.subscriptionBlocks[subKey];
|
subscriptionBlock = common.content.subscriptionBlocks[subKey];
|
||||||
subscriptionLength = subscriptionBlock.months * 30;
|
subscriptionLength = subscriptionBlock.months * 30;
|
||||||
|
|||||||
@@ -30,12 +30,14 @@ describe('Amazon Payments - Subscribe', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
group.purchased.plan.customerId = 'customer-id';
|
group.purchased.plan.customerId = 'customer-id';
|
||||||
group.purchased.plan.planId = subKey;
|
group.purchased.plan.planId = subKey;
|
||||||
await group.save();
|
await group.save();
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
await user.save();
|
||||||
|
|
||||||
amount = common.content.subscriptionBlocks[subKey].price;
|
amount = common.content.subscriptionBlocks[subKey].price;
|
||||||
billingAgreementId = 'billingAgreementId';
|
billingAgreementId = 'billingAgreementId';
|
||||||
@@ -246,11 +248,6 @@ describe('Amazon Payments - Subscribe', () => {
|
|||||||
user.guilds.push(groupId);
|
user.guilds.push(groupId);
|
||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
// Add existing users
|
|
||||||
user = new User();
|
|
||||||
user.guilds.push(groupId);
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
// Set expected amount
|
// Set expected amount
|
||||||
sub.key = 'group_monthly';
|
sub.key = 'group_monthly';
|
||||||
sub.price = 9;
|
sub.price = 9;
|
||||||
|
|||||||
@@ -12,11 +12,33 @@ const { i18n } = common;
|
|||||||
describe('Apple Payments', () => {
|
describe('Apple Payments', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
|
|
||||||
|
let iapSetupStub;
|
||||||
|
let iapValidateStub;
|
||||||
|
let iapIsValidatedStub;
|
||||||
|
let iapIsCanceledStub;
|
||||||
|
let iapIsExpiredStub;
|
||||||
|
let paymentBuySkuStub;
|
||||||
|
let iapGetPurchaseDataStub;
|
||||||
|
let validateGiftMessageStub;
|
||||||
|
let paymentsCreateSubscritionStub;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
iapSetupStub = sinon.stub(iap, 'setup').resolves();
|
||||||
|
iapValidateStub = sinon.stub(iap, 'validate').resolves({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
iap.setup.restore();
|
||||||
|
iap.validate.restore();
|
||||||
|
iap.isValidated.restore();
|
||||||
|
iap.isExpired.restore();
|
||||||
|
iap.isCanceled.restore();
|
||||||
|
iap.getPurchaseData.restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe('verifyPurchase', () => {
|
describe('verifyPurchase', () => {
|
||||||
let sku; let user; let token; let receipt; let
|
let sku; let user; let token; let receipt; let
|
||||||
headers;
|
headers;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let
|
|
||||||
iapGetPurchaseDataStub; let validateGiftMessageStub;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
token = 'testToken';
|
token = 'testToken';
|
||||||
@@ -25,13 +47,9 @@ describe('Apple Payments', () => {
|
|||||||
receipt = `{"token": "${token}", "productId": "${sku}"}`;
|
receipt = `{"token": "${token}", "productId": "${sku}"}`;
|
||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
|
||||||
.resolves({});
|
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||||
sinon.stub(iap, 'isExpired').returns(false);
|
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
|
||||||
sinon.stub(iap, 'isCanceled').returns(false);
|
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(false);
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{
|
.returns([{
|
||||||
productId: 'com.habitrpg.ios.Habitica.21gems',
|
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||||
@@ -42,12 +60,6 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
iap.setup.restore();
|
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
iap.isExpired.restore();
|
|
||||||
iap.isCanceled.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
|
||||||
payments.buySkuItem.restore();
|
payments.buySkuItem.restore();
|
||||||
gems.validateGiftMessage.restore();
|
gems.validateGiftMessage.restore();
|
||||||
});
|
});
|
||||||
@@ -209,9 +221,6 @@ describe('Apple Payments', () => {
|
|||||||
describe('subscribe', () => {
|
describe('subscribe', () => {
|
||||||
let sub; let sku; let user; let token; let receipt; let headers; let
|
let sub; let sku; let user; let token; let receipt; let headers; let
|
||||||
nextPaymentProcessing;
|
nextPaymentProcessing;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub;
|
|
||||||
let paymentsCreateSubscritionStub; let
|
|
||||||
iapGetPurchaseDataStub;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sub = common.content.subscriptionBlocks[subKey];
|
sub = common.content.subscriptionBlocks[subKey];
|
||||||
@@ -223,12 +232,10 @@ describe('Apple Payments', () => {
|
|||||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||||
user = new User();
|
user = new User();
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
|
||||||
.resolves({});
|
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
|
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
|
||||||
|
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(false);
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{
|
.returns([{
|
||||||
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
expirationDate: moment.utc().subtract({ day: 1 }).toDate(),
|
||||||
@@ -250,10 +257,6 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
iap.setup.restore();
|
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
|
||||||
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,6 +273,29 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error if no active subscription is found', async () => {
|
||||||
|
iap.isCanceled.restore();
|
||||||
|
iapIsCanceledStub = sinon.stub(iap, 'isCanceled')
|
||||||
|
.returns(true);
|
||||||
|
|
||||||
|
iap.getPurchaseData.restore();
|
||||||
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
|
.returns([{
|
||||||
|
expirationDate: moment.utc().add({ day: -2 }).toDate(),
|
||||||
|
purchaseDate: new Date(),
|
||||||
|
productId: 'subscription1month',
|
||||||
|
transactionId: token,
|
||||||
|
originalTransactionId: token,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await expect(applePayments.subscribe(user, receipt, headers, nextPaymentProcessing))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: applePayments.constants.RESPONSE_NO_ITEM_PURCHASED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const subOptions = [
|
const subOptions = [
|
||||||
{
|
{
|
||||||
sku: 'subscription1month',
|
sku: 'subscription1month',
|
||||||
@@ -574,8 +600,7 @@ describe('Apple Payments', () => {
|
|||||||
describe('cancelSubscribe ', () => {
|
describe('cancelSubscribe ', () => {
|
||||||
let user; let token; let receipt; let headers; let customerId; let
|
let user; let token; let receipt; let headers; let customerId; let
|
||||||
expirationDate;
|
expirationDate;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
|
let paymentCancelSubscriptionSpy;
|
||||||
paymentCancelSubscriptionSpy;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
token = 'test-token';
|
token = 'test-token';
|
||||||
@@ -584,8 +609,7 @@ describe('Apple Payments', () => {
|
|||||||
customerId = 'test-customerId';
|
customerId = 'test-customerId';
|
||||||
expirationDate = moment.utc();
|
expirationDate = moment.utc();
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
iapValidateStub.restore();
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
iapValidateStub = sinon.stub(iap, 'validate')
|
||||||
.resolves({
|
.resolves({
|
||||||
expirationDate,
|
expirationDate,
|
||||||
@@ -593,8 +617,8 @@ describe('Apple Payments', () => {
|
|||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ expirationDate: expirationDate.toDate() }]);
|
.returns([{ expirationDate: expirationDate.toDate() }]);
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated').returns(true);
|
||||||
sinon.stub(iap, 'isCanceled').returns(false);
|
iapIsCanceledStub = sinon.stub(iap, 'isCanceled').returns(false);
|
||||||
sinon.stub(iap, 'isExpired').returns(true);
|
iapIsExpiredStub = sinon.stub(iap, 'isExpired').returns(true);
|
||||||
user = new User();
|
user = new User();
|
||||||
user.profile.name = 'sender';
|
user.profile.name = 'sender';
|
||||||
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE;
|
||||||
@@ -606,13 +630,7 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
iap.setup.restore();
|
paymentCancelSubscriptionSpy.restore();
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
iap.isExpired.restore();
|
|
||||||
iap.isCanceled.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
|
||||||
payments.cancelSubscription.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if we are missing a subscription', async () => {
|
it('should throw an error if we are missing a subscription', async () => {
|
||||||
@@ -695,6 +713,8 @@ describe('Apple Payments', () => {
|
|||||||
expect(iapIsValidatedStub).to.be.calledWith({
|
expect(iapIsValidatedStub).to.be.calledWith({
|
||||||
expirationDate,
|
expirationDate,
|
||||||
});
|
});
|
||||||
|
expect(iapIsCanceledStub).to.be.calledOnce;
|
||||||
|
expect(iapIsExpiredStub).to.be.calledOnce;
|
||||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||||
|
|
||||||
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
|
expect(paymentCancelSubscriptionSpy).to.be.calledOnce;
|
||||||
|
|||||||
@@ -11,12 +11,36 @@ const { i18n } = common;
|
|||||||
|
|
||||||
describe('Google Payments', () => {
|
describe('Google Payments', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
|
let iapSetupStub;
|
||||||
|
let iapValidateStub;
|
||||||
|
let iapIsValidatedStub;
|
||||||
|
let paymentBuySkuStub;
|
||||||
|
let validateGiftMessageStub;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
iapSetupStub = sinon.stub(iap, 'setup')
|
||||||
|
.resolves();
|
||||||
|
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
||||||
|
.returns(true);
|
||||||
|
sinon.stub(iap, 'isCanceled').returns(false);
|
||||||
|
sinon.stub(iap, 'isExpired').returns(false);
|
||||||
|
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
||||||
|
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
iap.setup.restore();
|
||||||
|
iap.validate.restore();
|
||||||
|
iap.isValidated.restore();
|
||||||
|
iap.isCanceled.restore();
|
||||||
|
iap.isExpired.restore();
|
||||||
|
payments.buySkuItem.restore();
|
||||||
|
gems.validateGiftMessage.restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe('verifyPurchase', () => {
|
describe('verifyPurchase', () => {
|
||||||
let sku; let user; let token; let receipt; let signature; let
|
let sku; let user; let token; let receipt; let signature; let
|
||||||
headers;
|
headers;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
|
||||||
paymentBuySkuStub; let validateGiftMessageStub;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
sku = 'com.habitrpg.android.habitica.iap.21gems';
|
||||||
@@ -25,21 +49,7 @@ describe('Google Payments', () => {
|
|||||||
signature = '';
|
signature = '';
|
||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
|
iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku });
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
|
||||||
.returns(true);
|
|
||||||
paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({});
|
|
||||||
validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
iap.setup.restore();
|
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
payments.buySkuItem.restore();
|
|
||||||
gems.validateGiftMessage.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if receipt is invalid', async () => {
|
it('should throw an error if receipt is invalid', async () => {
|
||||||
@@ -160,8 +170,7 @@ describe('Google Payments', () => {
|
|||||||
describe('subscribe', () => {
|
describe('subscribe', () => {
|
||||||
let sub; let sku; let user; let token; let receipt; let signature; let headers; let
|
let sub; let sku; let user; let token; let receipt; let signature; let headers; let
|
||||||
nextPaymentProcessing;
|
nextPaymentProcessing;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
|
let paymentsCreateSubscritionStub;
|
||||||
paymentsCreateSubscritionStub;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sub = common.content.subscriptionBlocks[subKey];
|
sub = common.content.subscriptionBlocks[subKey];
|
||||||
@@ -173,19 +182,12 @@ describe('Google Payments', () => {
|
|||||||
signature = '';
|
signature = '';
|
||||||
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
nextPaymentProcessing = moment.utc().add({ days: 2 });
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
iapValidateStub = sinon.stub(iap, 'validate')
|
||||||
.resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
|
||||||
.returns(true);
|
|
||||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
iap.setup.restore();
|
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
payments.createSubscription.restore();
|
payments.createSubscription.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,7 +245,7 @@ describe('Google Payments', () => {
|
|||||||
describe('cancelSubscribe ', () => {
|
describe('cancelSubscribe ', () => {
|
||||||
let user; let token; let receipt; let signature; let headers; let customerId; let
|
let user; let token; let receipt; let signature; let headers; let customerId; let
|
||||||
expirationDate;
|
expirationDate;
|
||||||
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let iapGetPurchaseDataStub; let
|
let iapGetPurchaseDataStub; let
|
||||||
paymentCancelSubscriptionSpy;
|
paymentCancelSubscriptionSpy;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -253,17 +255,12 @@ describe('Google Payments', () => {
|
|||||||
signature = '';
|
signature = '';
|
||||||
customerId = 'test-customerId';
|
customerId = 'test-customerId';
|
||||||
expirationDate = moment.utc();
|
expirationDate = moment.utc();
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iap, 'setup')
|
|
||||||
.resolves();
|
|
||||||
iapValidateStub = sinon.stub(iap, 'validate')
|
iapValidateStub = sinon.stub(iap, 'validate')
|
||||||
.resolves({
|
.resolves({
|
||||||
expirationDate,
|
expirationDate,
|
||||||
});
|
});
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ expirationDate: expirationDate.toDate(), autoRenewing: false }]);
|
.returns([{ expirationDate: expirationDate.toDate(), autoRenewing: false }]);
|
||||||
iapIsValidatedStub = sinon.stub(iap, 'isValidated')
|
|
||||||
.returns(true);
|
|
||||||
|
|
||||||
user = new User();
|
user = new User();
|
||||||
user.profile.name = 'sender';
|
user.profile.name = 'sender';
|
||||||
@@ -276,9 +273,6 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
iap.setup.restore();
|
|
||||||
iap.validate.restore();
|
|
||||||
iap.isValidated.restore();
|
|
||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
payments.cancelSubscription.restore();
|
payments.cancelSubscription.restore();
|
||||||
});
|
});
|
||||||
@@ -308,6 +302,8 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should cancel a user subscription', async () => {
|
it('should cancel a user subscription', async () => {
|
||||||
|
iap.isCanceled.restore();
|
||||||
|
iap.isCanceled = sinon.stub(iap, 'isCanceled').returns(true);
|
||||||
await googlePayments.cancelSubscribe(user, headers);
|
await googlePayments.cancelSubscribe(user, headers);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
@@ -332,11 +328,20 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should cancel a user subscription with multiple inactive subscriptions', async () => {
|
it('should cancel a user subscription with multiple inactive subscriptions', async () => {
|
||||||
|
iap.isCanceled.restore();
|
||||||
|
iap.isCanceled = sinon.stub(iap, 'isCanceled').returns(true);
|
||||||
const laterDate = moment.utc().add(7, 'days');
|
const laterDate = moment.utc().add(7, 'days');
|
||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ expirationDate, autoRenewing: false },
|
.returns([{
|
||||||
{ expirationDate: laterDate, autoRenewing: false },
|
startTimeMillis: expirationDate.valueOf(),
|
||||||
|
expirationDate,
|
||||||
|
autoRenewing: false,
|
||||||
|
}, {
|
||||||
|
startTimeMillis: laterDate.valueOf(),
|
||||||
|
expirationDate: laterDate,
|
||||||
|
autoRenewing: false,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
await googlePayments.cancelSubscribe(user, headers);
|
await googlePayments.cancelSubscribe(user, headers);
|
||||||
|
|
||||||
@@ -365,7 +370,12 @@ describe('Google Payments', () => {
|
|||||||
iap.getPurchaseData.restore();
|
iap.getPurchaseData.restore();
|
||||||
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
|
||||||
.returns([{ autoRenewing: true }]);
|
.returns([{ autoRenewing: true }]);
|
||||||
await googlePayments.cancelSubscribe(user, headers);
|
await expect(googlePayments.cancelSubscribe(user, headers))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: googlePayments.constants.RESPONSE_STILL_VALID,
|
||||||
|
});
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
@@ -388,8 +398,12 @@ describe('Google Payments', () => {
|
|||||||
.returns([{ expirationDate, autoRenewing: false },
|
.returns([{ expirationDate, autoRenewing: false },
|
||||||
{ autoRenewing: true },
|
{ autoRenewing: true },
|
||||||
{ expirationDate, autoRenewing: false }]);
|
{ expirationDate, autoRenewing: false }]);
|
||||||
await googlePayments.cancelSubscribe(user, headers);
|
await expect(googlePayments.cancelSubscribe(user, headers))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: googlePayments.constants.RESPONSE_STILL_VALID,
|
||||||
|
});
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
|
||||||
|
|||||||
@@ -128,11 +128,12 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
expect(publicGroup.purchased.plan.planId).to.not.exist;
|
expect(publicGroup.purchased.plan.planId).to.not.exist;
|
||||||
data.groupId = publicGroup._id;
|
data.groupId = publicGroup._id;
|
||||||
|
|
||||||
|
// Public Guilds are no longer even findable
|
||||||
await expect(api.createSubscription(data))
|
await expect(api.createSubscription(data))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 404,
|
||||||
name: 'NotAuthorized',
|
name: 'NotFound',
|
||||||
message: i18n.t('onlyPrivateGuildsCanUpgrade'),
|
message: i18n.t('groupNotFound'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedGroup = await Group.findById(publicGroup._id).exec();
|
const updatedGroup = await Group.findById(publicGroup._id).exec();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import moment from 'moment';
|
|||||||
import * as sender from '../../../../../website/server/libs/email';
|
import * as sender from '../../../../../website/server/libs/email';
|
||||||
import common from '../../../../../website/common';
|
import common from '../../../../../website/common';
|
||||||
import api from '../../../../../website/server/libs/payments/payments';
|
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 * as notifications from '../../../../../website/server/libs/pushNotifications';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import { translate as t } from '../../../../helpers/api-integration/v3';
|
import { translate as t } from '../../../../helpers/api-integration/v3';
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import * as worldState from '../../../../../website/server/libs/worldState';
|
import * as worldState from '../../../../../website/server/libs/worldState';
|
||||||
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
||||||
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
|
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
|
||||||
|
import { SubscriptionEventModel } from '../../../../../website/server/models/analytics/subscriptionEvent';
|
||||||
|
|
||||||
describe('payments/index', () => {
|
describe('payments/index', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -36,8 +36,6 @@ describe('payments/index', () => {
|
|||||||
|
|
||||||
sandbox.stub(sender, 'sendTxn');
|
sandbox.stub(sender, 'sendTxn');
|
||||||
sandbox.stub(user, 'sendMessage');
|
sandbox.stub(user, 'sendMessage');
|
||||||
sandbox.stub(analytics.mockAnalyticsService, 'trackPurchase');
|
|
||||||
sandbox.stub(analytics.mockAnalyticsService, 'track');
|
|
||||||
sandbox.stub(notifications, 'sendNotification');
|
sandbox.stub(notifications, 'sendNotification');
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@@ -97,6 +95,16 @@ describe('payments/index', () => {
|
|||||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
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 () => {
|
it('adds extra months to an existing subscription', async () => {
|
||||||
recipient.purchased.plan = plan;
|
recipient.purchased.plan = plan;
|
||||||
|
|
||||||
@@ -298,28 +306,6 @@ describe('payments/index', () => {
|
|||||||
expect(notifications.sendNotification).to.be.calledOnce;
|
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', () => {
|
context('No Active Promotion', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(worldState, 'getCurrentEventList').returns([]);
|
sinon.stub(worldState, 'getCurrentEventList').returns([]);
|
||||||
@@ -455,6 +441,16 @@ describe('payments/index', () => {
|
|||||||
expect(user.purchased.plan.dateCreated).to.exist;
|
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 () => {
|
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||||
expect(user.purchased.plan.dateCreated).to.not.exist;
|
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');
|
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', () => {
|
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 () => {
|
it('from basic_earned to basic_6mo', async () => {
|
||||||
data.sub.key = 'basic_earned';
|
data.sub.key = 'basic_earned';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
@@ -608,6 +599,23 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Downgrades subscription', () => {
|
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 () => {
|
it('from basic_6mo to basic_earned', async () => {
|
||||||
data.sub.key = 'basic_6mo';
|
data.sub.key = 'basic_6mo';
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
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
|
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 () => {
|
it('adds extraMonths to dateTerminated value', async () => {
|
||||||
user.purchased.plan.extraMonths = 2;
|
user.purchased.plan.extraMonths = 2;
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,15 @@ describe('paypal - subscribeCancel', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
group.purchased.plan.customerId = groupCustomerId;
|
group.purchased.plan.customerId = groupCustomerId;
|
||||||
group.purchased.plan.planId = subKey;
|
group.purchased.plan.planId = subKey;
|
||||||
group.purchased.plan.lastBillingDate = new Date();
|
group.purchased.plan.lastBillingDate = new Date();
|
||||||
await group.save();
|
await group.save();
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
await user.save();
|
||||||
|
|
||||||
nextBillingDate = new Date();
|
nextBillingDate = new Date();
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ describe('Stripe - Checkout', () => {
|
|||||||
const group = generateGroup({
|
const group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
const groupId = group._id;
|
const groupId = group._id;
|
||||||
@@ -376,11 +376,13 @@ describe('Stripe - Checkout', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
groupId = group._id;
|
groupId = group._id;
|
||||||
await group.save();
|
await group.save();
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
await user.save();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if user is not allowed to change group plan', async () => {
|
it('throws if user is not allowed to change group plan', async () => {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ describe('Stripe - Subscriptions', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
groupId = group._id;
|
groupId = group._id;
|
||||||
@@ -315,12 +315,14 @@ describe('Stripe - Subscriptions', () => {
|
|||||||
group = generateGroup({
|
group = generateGroup({
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
leader: user._id,
|
leader: user._id,
|
||||||
});
|
});
|
||||||
group.purchased.plan.customerId = 'customer-id';
|
group.purchased.plan.customerId = 'customer-id';
|
||||||
group.purchased.plan.planId = subKey;
|
group.purchased.plan.planId = subKey;
|
||||||
await group.save();
|
await group.save();
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
await user.save();
|
||||||
|
|
||||||
groupId = group._id;
|
groupId = group._id;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import nconf from 'nconf';
|
||||||
|
import requireAgain from 'require-again';
|
||||||
import {
|
import {
|
||||||
generateRes,
|
generateRes,
|
||||||
generateReq,
|
generateReq,
|
||||||
} from '../../../helpers/api-unit.helper';
|
} from '../../../helpers/api-unit.helper';
|
||||||
import { authWithHeaders as authWithHeadersFactory } from '../../../../website/server/middlewares/auth';
|
|
||||||
|
const authPath = '../../../../website/server/middlewares/auth';
|
||||||
|
|
||||||
describe('auth middleware', () => {
|
describe('auth middleware', () => {
|
||||||
let res; let req; let
|
let res; let req; let
|
||||||
@@ -16,6 +19,7 @@ describe('auth middleware', () => {
|
|||||||
|
|
||||||
describe('auth with headers', () => {
|
describe('auth with headers', () => {
|
||||||
it('allows to specify a list of user field that we do not want to load', done => {
|
it('allows to specify a list of user field that we do not want to load', done => {
|
||||||
|
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||||
const authWithHeaders = authWithHeadersFactory({
|
const authWithHeaders = authWithHeadersFactory({
|
||||||
userFieldsToExclude: ['items'],
|
userFieldsToExclude: ['items'],
|
||||||
});
|
});
|
||||||
@@ -35,6 +39,7 @@ describe('auth middleware', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('makes sure some fields are always included', done => {
|
it('makes sure some fields are always included', done => {
|
||||||
|
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||||
const authWithHeaders = authWithHeadersFactory({
|
const authWithHeaders = authWithHeadersFactory({
|
||||||
userFieldsToExclude: [
|
userFieldsToExclude: [
|
||||||
'items', 'auth.timestamps',
|
'items', 'auth.timestamps',
|
||||||
@@ -60,5 +65,57 @@ describe('auth middleware', () => {
|
|||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('errors with InvalidCredentialsError and code when token is wrong', done => {
|
||||||
|
const authWithHeadersFactory = requireAgain(authPath).authWithHeaders;
|
||||||
|
const authWithHeaders = authWithHeadersFactory({ userFieldsToExclude: [] });
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = user._id;
|
||||||
|
req.headers['x-api-key'] = 'totally-wrong-token';
|
||||||
|
|
||||||
|
authWithHeaders(req, res, err => {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(err.name).to.equal('InvalidCredentialsError');
|
||||||
|
expect(err.code).to.equal('invalid_credentials');
|
||||||
|
expect(err.message).to.equal(res.t('invalidCredentials'));
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when ENFORCE_CLIENT_HEADER is true', () => {
|
||||||
|
let authFactory;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('ENFORCE_CLIENT_HEADER').returns('true');
|
||||||
|
authFactory = requireAgain(authPath).authWithHeaders;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors with missingClientHeader when x-client header is not present', done => {
|
||||||
|
const authWithHeaders = authFactory({ userFieldsToExclude: [] });
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = user._id;
|
||||||
|
req.headers['x-api-key'] = user;
|
||||||
|
authWithHeaders(req, res, err => {
|
||||||
|
expect(err).to.exist;
|
||||||
|
expect(err.name).to.equal('BadRequest');
|
||||||
|
expect(err.message).to.equal(res.t('missingClientHeader'));
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows request to pass when x-client header is present', done => {
|
||||||
|
const authWithHeaders = authFactory({ userFieldsToExclude: [] });
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = user._id;
|
||||||
|
req.headers['x-api-key'] = user.apiToken;
|
||||||
|
req.headers['x-client'] = 'habitica-web';
|
||||||
|
|
||||||
|
authWithHeaders(req, res, err => {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.locals.user).to.exist;
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import nconf from 'nconf';
|
||||||
|
import requireAgain from 'require-again';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
import { Forbidden } from '../../../../website/server/libs/errors';
|
||||||
|
import { apiError } from '../../../../website/server/libs/apiError';
|
||||||
|
import { model as Blocker } from '../../../../website/server/models/blocker';
|
||||||
|
|
||||||
|
function checkIPBlockedErrorThrown (next) {
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0].message).to.equal(apiError('ipAddressBlocked'));
|
||||||
|
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkClientBlockedErrorThrown (next) {
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(calledWith[0].message).to.equal(apiError('clientBlocked'));
|
||||||
|
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkErrorNotThrown (next) {
|
||||||
|
expect(next).to.have.been.calledOnce;
|
||||||
|
const calledWith = next.getCall(0).args;
|
||||||
|
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Blocker middleware', () => {
|
||||||
|
const pathToBlocker = '../../../../website/server/middlewares/blocker';
|
||||||
|
|
||||||
|
let res; let req; let next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Blocking IPs', () => {
|
||||||
|
it('is disabled when the env var is not defined', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined);
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is disabled when the env var is an empty string', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is disabled when the env var contains comma separated empty strings', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , ');
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when the ip does not match', () => {
|
||||||
|
req.ip = '192.168.1.1';
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when the blocker IP does not match', async () => {
|
||||||
|
req.ip = '192.168.1.1';
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.2' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when a client is blocked', async () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: '192.168.1.1' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when the blocker IP is blocked', async () => {
|
||||||
|
req.ip = '192.168.1.1';
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.1' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkIPBlockedErrorThrown(next);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Blocking clients', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
||||||
|
req.headers['x-client'] = 'test-client';
|
||||||
|
});
|
||||||
|
it('is disabled when no clients are blocked', () => {
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when the client does not match', async () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when the client is blocked', async () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkClientBlockedErrorThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw when an ip is blocked', async () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: 'test-client' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the list when data changes', async () => {
|
||||||
|
let blockCallback;
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
blockCallback = callback;
|
||||||
|
if (event === 'change') {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const attachBlocker = requireAgain(pathToBlocker).default;
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
checkErrorNotThrown(next);
|
||||||
|
blockCallback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } });
|
||||||
|
attachBlocker(req, res, next);
|
||||||
|
expect(next).to.have.been.calledTwice;
|
||||||
|
const calledWith = next.getCall(1).args;
|
||||||
|
expect(calledWith[0].message).to.equal(apiError('clientBlocked'));
|
||||||
|
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import nconf from 'nconf';
|
|
||||||
import requireAgain from 'require-again';
|
|
||||||
import {
|
|
||||||
generateRes,
|
|
||||||
generateReq,
|
|
||||||
generateNext,
|
|
||||||
} from '../../../helpers/api-unit.helper';
|
|
||||||
import { Forbidden } from '../../../../website/server/libs/errors';
|
|
||||||
import { apiError } from '../../../../website/server/libs/apiError';
|
|
||||||
|
|
||||||
function checkErrorThrown (next) {
|
|
||||||
expect(next).to.have.been.calledOnce;
|
|
||||||
const calledWith = next.getCall(0).args;
|
|
||||||
expect(calledWith[0].message).to.equal(apiError('ipAddressBlocked'));
|
|
||||||
expect(calledWith[0] instanceof Forbidden).to.equal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkErrorNotThrown (next) {
|
|
||||||
expect(next).to.have.been.calledOnce;
|
|
||||||
const calledWith = next.getCall(0).args;
|
|
||||||
expect(typeof calledWith[0] === 'undefined').to.equal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ipBlocker middleware', () => {
|
|
||||||
const pathToIpBlocker = '../../../../website/server/middlewares/ipBlocker';
|
|
||||||
|
|
||||||
let res; let req; let next;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
res = generateRes();
|
|
||||||
req = generateReq();
|
|
||||||
next = generateNext();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is disabled when the env var is not defined', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined);
|
|
||||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
|
||||||
attachIpBlocker(req, res, next);
|
|
||||||
|
|
||||||
checkErrorNotThrown(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is disabled when the env var is an empty string', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
|
|
||||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
|
||||||
attachIpBlocker(req, res, next);
|
|
||||||
|
|
||||||
checkErrorNotThrown(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is disabled when the env var contains comma separated empty strings', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , ');
|
|
||||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
|
||||||
attachIpBlocker(req, res, next);
|
|
||||||
|
|
||||||
checkErrorNotThrown(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not throw when the ip does not match', () => {
|
|
||||||
req.ip = '192.168.1.1';
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
|
|
||||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
|
||||||
attachIpBlocker(req, res, next);
|
|
||||||
|
|
||||||
checkErrorNotThrown(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws when the ip is blocked', () => {
|
|
||||||
req.ip = '192.168.1.1';
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
|
|
||||||
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
|
|
||||||
attachIpBlocker(req, res, next);
|
|
||||||
|
|
||||||
checkErrorThrown(next);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -32,7 +32,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('is disabled when the env var is not defined', () => {
|
it('is disabled when the env var is not defined', () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
attachRateLimiter(req, res, next);
|
attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
expect(next).to.have.been.calledOnce;
|
expect(next).to.have.been.calledOnce;
|
||||||
@@ -43,7 +44,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
|
|
||||||
it('is disabled when the env var is an not "true"', () => {
|
it('is disabled when the env var is an not "true"', () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
attachRateLimiter(req, res, next);
|
attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
expect(next).to.have.been.calledOnce;
|
expect(next).to.have.been.calledOnce;
|
||||||
@@ -55,7 +57,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('does not throw when there are available points', async () => {
|
it('does not throw when there are available points', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
expect(next).to.have.been.calledOnce;
|
expect(next).to.have.been.calledOnce;
|
||||||
@@ -77,7 +80,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
sandbox.stub(RateLimiterMemory.prototype, 'consume')
|
||||||
.returns(Promise.reject(new Error('Unknown error.')));
|
.returns(Promise.reject(new Error('Unknown error.')));
|
||||||
|
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
expect(next).to.have.been.calledOnce;
|
expect(next).to.have.been.calledOnce;
|
||||||
@@ -92,7 +96,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('does not throw when LIVELINESS_PROBE_KEY is correct', async () => {
|
it('does not throw when LIVELINESS_PROBE_KEY is correct', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
req.query.liveliness = 'abc';
|
req.query.liveliness = 'abc';
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
@@ -107,7 +112,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
req.query.liveliness = 'das';
|
req.query.liveliness = 'das';
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
@@ -124,7 +130,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|
||||||
@@ -140,7 +147,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
|
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
req.query.liveliness = '';
|
req.query.liveliness = '';
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
@@ -156,7 +164,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('throws when there are no available points remaining', async () => {
|
it('throws when there are no available points remaining', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
// call for 31 times
|
// call for 31 times
|
||||||
for (let i = 0; i < 31; i += 1) {
|
for (let i = 0; i < 31; i += 1) {
|
||||||
@@ -180,7 +189,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('uses the user id if supplied or the ip address', async () => {
|
it('uses the user id if supplied or the ip address', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
req.ip = 1;
|
req.ip = 1;
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
@@ -210,7 +220,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('applies increased cost for registration calls with and without user id', async () => {
|
it('applies increased cost for registration calls with and without user id', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
|
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
req.path = '/api/v4/user/auth/local/register';
|
req.path = '/api/v4/user/auth/local/register';
|
||||||
|
|
||||||
req.ip = 1;
|
req.ip = 1;
|
||||||
@@ -241,7 +252,8 @@ describe('rateLimiter middleware', () => {
|
|||||||
it('applies increased cost for unauthenticated API calls', async () => {
|
it('applies increased cost for unauthenticated API calls', async () => {
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
|
||||||
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
|
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
|
||||||
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
|
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
|
||||||
|
const attachRateLimiter = setupRateLimiter();
|
||||||
|
|
||||||
req.ip = 1;
|
req.ip = 1;
|
||||||
await attachRateLimiter(req, res, next);
|
await attachRateLimiter(req, res, next);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
SPAM_MESSAGE_LIMIT,
|
SPAM_MESSAGE_LIMIT,
|
||||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
SPAM_WINDOW_LENGTH,
|
SPAM_WINDOW_LENGTH,
|
||||||
|
MAX_CHAT_COUNT,
|
||||||
|
MAX_SUBBED_GROUP_CHAT_COUNT,
|
||||||
INVITES_LIMIT,
|
INVITES_LIMIT,
|
||||||
model as Group,
|
model as Group,
|
||||||
} from '../../../../website/server/models/group';
|
} from '../../../../website/server/models/group';
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
import * as email from '../../../../website/server/libs/email';
|
import * as email from '../../../../website/server/libs/email';
|
||||||
import { TAVERN_ID } from '../../../../website/common/script/constants';
|
import { TAVERN_ID } from '../../../../website/common/script/constants';
|
||||||
import shared from '../../../../website/common';
|
import shared from '../../../../website/common';
|
||||||
|
import { chatModel as Chat } from '../../../../website/server/models/message';
|
||||||
|
|
||||||
describe('Group Model', () => {
|
describe('Group Model', () => {
|
||||||
let party; let questLeader; let participatingMember;
|
let party; let questLeader; let participatingMember;
|
||||||
@@ -1356,6 +1359,29 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getEffectiveChatLimit', () => {
|
||||||
|
it('returns the correct chat limit', () => {
|
||||||
|
const group = new Group();
|
||||||
|
expect(group.getEffectiveChatLimit()).to.eql(MAX_CHAT_COUNT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the passed limit if it is lower than the max', () => {
|
||||||
|
const group = new Group();
|
||||||
|
expect(group.getEffectiveChatLimit(10)).to.eql(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the max if the passed limit is higher', () => {
|
||||||
|
const group = new Group();
|
||||||
|
expect(group.getEffectiveChatLimit(MAX_CHAT_COUNT + 10)).to.eql(MAX_CHAT_COUNT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the max for group plans', () => {
|
||||||
|
const group = new Group();
|
||||||
|
group.purchased.plan.customerId = '110002222333';
|
||||||
|
expect(group.getEffectiveChatLimit()).to.eql(MAX_SUBBED_GROUP_CHAT_COUNT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#sendChat', () => {
|
describe('#sendChat', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox.spy(User, 'updateOne');
|
sandbox.spy(User, 'updateOne');
|
||||||
@@ -1462,6 +1488,34 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#trimChat', () => {
|
||||||
|
it('Only checks last message when not enough messages to trim', async () => {
|
||||||
|
sandbox.spy(Chat, 'find');
|
||||||
|
sandbox.spy(Chat, 'deleteMany');
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
await party.trimChat();
|
||||||
|
|
||||||
|
expect(Chat.find).to.be.calledOnce;
|
||||||
|
expect(Chat.deleteMany).to.not.be.called;
|
||||||
|
expect(await Chat.countDocuments({ groupId: party._id })).to.eql(3);
|
||||||
|
});
|
||||||
|
it('Deletes messages over the limit', async () => {
|
||||||
|
sandbox.spy(Chat, 'find');
|
||||||
|
sandbox.spy(Chat, 'deleteMany');
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
|
||||||
|
|
||||||
|
await party.trimChat(1);
|
||||||
|
|
||||||
|
expect(Chat.find).to.be.calledOnce;
|
||||||
|
expect(Chat.deleteMany).to.be.calledOnce;
|
||||||
|
expect(await Chat.countDocuments({ groupId: party._id })).to.eql(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#startQuest', () => {
|
describe('#startQuest', () => {
|
||||||
context('Failure Conditions', () => {
|
context('Failure Conditions', () => {
|
||||||
it('throws an error if group is not a party', async () => {
|
it('throws an error if group is not a party', async () => {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import requireAgain from 'require-again';
|
||||||
import { model as User } from '../../../../website/server/models/user';
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
import { model as NewsPost } from '../../../../website/server/models/newsPost';
|
import { model as NewsPost } from '../../../../website/server/models/newsPost';
|
||||||
import { model as Group } from '../../../../website/server/models/group';
|
import { model as Group } from '../../../../website/server/models/group';
|
||||||
|
import { model as Blocker } from '../../../../website/server/models/blocker';
|
||||||
import common from '../../../../website/common';
|
import common from '../../../../website/common';
|
||||||
|
|
||||||
|
const pathToUserSchema = '../../../../website/server/models/user/schema';
|
||||||
|
|
||||||
describe('User Model', () => {
|
describe('User Model', () => {
|
||||||
describe('.toJSON()', () => {
|
describe('.toJSON()', () => {
|
||||||
it('keeps user._tmp when calling .toJSON', () => {
|
it('keeps user._tmp when calling .toJSON', () => {
|
||||||
@@ -912,4 +916,73 @@ describe('User Model', () => {
|
|||||||
expect(user.toJSON().flags.newStuff).to.equal(true);
|
expect(user.toJSON().flags.newStuff).to.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('validates email', () => {
|
||||||
|
it('does not throw an error for a valid email', () => {
|
||||||
|
const user = new User();
|
||||||
|
user.auth.local.email = 'hello@example.com';
|
||||||
|
const errors = user.validateSync();
|
||||||
|
expect(errors.errors['auth.local.email']).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if email is not valid', () => {
|
||||||
|
const user = new User();
|
||||||
|
user.auth.local.email = 'invalid-email';
|
||||||
|
const errors = user.validateSync();
|
||||||
|
expect(errors.errors['auth.local.email'].message).to.equal(common.i18n.t('invalidEmail'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if email is using a restricted domain', () => {
|
||||||
|
const user = new User();
|
||||||
|
user.auth.local.email = 'scammer@habitica.com';
|
||||||
|
const errors = user.validateSync();
|
||||||
|
expect(errors.errors['auth.local.email'].message).to.equal(common.i18n.t('invalidEmailDomain', { domains: 'habitica.com, habitrpg.com' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if email was blocked specifically', () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@example.com' } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||||
|
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||||
|
expect(valid).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if email domain was blocked', () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: '@example.com' } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||||
|
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||||
|
expect(valid).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if user portion of email was blocked', () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@' } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||||
|
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('blocked@example.com'));
|
||||||
|
expect(valid).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw an error if email is not blocked', () => {
|
||||||
|
sandbox.stub(Blocker, 'watchBlockers').returns({
|
||||||
|
on: (event, callback) => {
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: '@example.com' } });
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'blocked@' } });
|
||||||
|
callback({ operation: 'add', blocker: { type: 'email', area: 'full', value: 'bad@test.com' } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const schema = requireAgain(pathToUserSchema).UserSchema;
|
||||||
|
const valid = schema.paths['auth.local.email'].options.validate.every(v => v.validator('good@test.com'));
|
||||||
|
expect(valid).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,5 +50,59 @@ describe('UserNotification Model', () => {
|
|||||||
expect(safeNotifications[0].type).to.equal('NEW_CHAT_MESSAGE');
|
expect(safeNotifications[0].type).to.equal('NEW_CHAT_MESSAGE');
|
||||||
expect(safeNotifications[0].id).to.equal('123');
|
expect(safeNotifications[0].id).to.equal('123');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes duplicate STREAK_ACHIEVEMENT notifications', () => {
|
||||||
|
// Fixes issue #13325 - Users receiving duplicate streak achievement notifications
|
||||||
|
const notifications = [
|
||||||
|
new UserNotification({
|
||||||
|
type: 'STREAK_ACHIEVEMENT',
|
||||||
|
id: 123,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
new UserNotification({
|
||||||
|
type: 'STREAK_ACHIEVEMENT',
|
||||||
|
id: 456,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
new UserNotification({
|
||||||
|
type: 'CRON',
|
||||||
|
id: 789,
|
||||||
|
data: {},
|
||||||
|
}), // different type, should be kept
|
||||||
|
];
|
||||||
|
|
||||||
|
const safeNotifications = UserNotification.cleanupCorruptData(notifications);
|
||||||
|
expect(safeNotifications.length).to.equal(2);
|
||||||
|
expect(safeNotifications[0].type).to.equal('STREAK_ACHIEVEMENT');
|
||||||
|
expect(safeNotifications[0].id).to.equal('123');
|
||||||
|
expect(safeNotifications[1].type).to.equal('CRON');
|
||||||
|
expect(safeNotifications[1].id).to.equal('789');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles multiple STREAK_ACHIEVEMENT duplicates correctly', () => {
|
||||||
|
// Test case: 3 duplicate STREAK_ACHIEVEMENT notifications
|
||||||
|
const notifications = [
|
||||||
|
new UserNotification({
|
||||||
|
type: 'STREAK_ACHIEVEMENT',
|
||||||
|
id: 111,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
new UserNotification({
|
||||||
|
type: 'STREAK_ACHIEVEMENT',
|
||||||
|
id: 222,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
new UserNotification({
|
||||||
|
type: 'STREAK_ACHIEVEMENT',
|
||||||
|
id: 333,
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const safeNotifications = UserNotification.cleanupCorruptData(notifications);
|
||||||
|
expect(safeNotifications.length).to.equal(1);
|
||||||
|
expect(safeNotifications[0].type).to.equal('STREAK_ACHIEVEMENT');
|
||||||
|
expect(safeNotifications[0].id).to.equal('111'); // Keep first one
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import { model as Group } from '../../../../../website/server/models/group';
|
||||||
|
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('POST /challenges/:challengeId/join', () => {
|
describe('POST /challenges/:challengeId/join', () => {
|
||||||
it('returns error when challengeId is not a valid UUID', async () => {
|
it('returns error when challengeId is not a valid UUID', async () => {
|
||||||
@@ -27,6 +29,37 @@ describe('POST /challenges/:challengeId/join', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('public Guild', () => {
|
||||||
|
let group;
|
||||||
|
let groupLeader;
|
||||||
|
let members;
|
||||||
|
let challenge;
|
||||||
|
before(async () => {
|
||||||
|
({ group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test group',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
challenge = await generateChallenge(groupLeader, group);
|
||||||
|
// Creation API is shut down, we need to simulate an extant public group
|
||||||
|
await Group.updateOne({ _id: group._id }, { $set: { privacy: 'public' }, $unset: { 'purchased.plan': 1 } }).exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when challengeId is in an old public Guild', async () => {
|
||||||
|
const authorizedUser = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
|
|
||||||
|
await expect(authorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('challengeNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context('Joining a valid challenge', () => {
|
context('Joining a valid challenge', () => {
|
||||||
let groupLeader;
|
let groupLeader;
|
||||||
let group;
|
let group;
|
||||||
@@ -66,6 +99,15 @@ describe('POST /challenges/:challengeId/join', () => {
|
|||||||
expect(res.name).to.equal(challenge.name);
|
expect(res.name).to.equal(challenge.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('succeeds when it\'s a Tavern challenge, even if the user isn\'t a "member" of Tavern', async () => {
|
||||||
|
const tavern = await groupLeader.get(`/groups/${TAVERN_ID}`);
|
||||||
|
const tavernChallenge = await generateChallenge(groupLeader, tavern, { prize: 1 });
|
||||||
|
const generalUser = await generateUser();
|
||||||
|
|
||||||
|
const res = await generalUser.post(`/challenges/${tavernChallenge._id}/join`);
|
||||||
|
expect(res.name).to.equal(tavernChallenge.name);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns challenge data', async () => {
|
it('returns challenge data', async () => {
|
||||||
const res = await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
const res = await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ describe('GET /groups/:groupId/chat', () => {
|
|||||||
|
|
||||||
it('returns error if user attempts to fetch a sunset Guild', async () => {
|
it('returns error if user attempts to fetch a sunset Guild', async () => {
|
||||||
await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({
|
await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 404,
|
||||||
error: 'BadRequest',
|
error: 'NotFound',
|
||||||
message: t('featureRetired'),
|
message: t('groupNotFound'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ describe('POST /chat/:chatId/like', () => {
|
|||||||
|
|
||||||
await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
|
await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 404,
|
||||||
error: 'BadRequest',
|
error: 'NotFound',
|
||||||
message: t('featureRetired'),
|
message: t('groupNotFound'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
} from '../../../../../website/server/models/group';
|
} from '../../../../../website/server/models/group';
|
||||||
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
|
import { MAX_MESSAGE_LENGTH, CHAT_FLAG_FROM_SHADOW_MUTE } from '../../../../../website/common/script/constants';
|
||||||
import * as email from '../../../../../website/server/libs/email';
|
import * as email from '../../../../../website/server/libs/email';
|
||||||
|
|
||||||
describe('POST /chat', () => {
|
describe('POST /chat', () => {
|
||||||
@@ -80,17 +80,20 @@ describe('POST /chat', () => {
|
|||||||
member.updateOne({ 'flags.chatRevoked': false });
|
member.updateOne({ 'flags.chatRevoked': false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
it('errors when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||||
await member.updateOne({
|
await member.updateOne({
|
||||||
'flags.chatRevoked': true,
|
'flags.chatRevoked': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
await expect(member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
expect(message.message.id).to.exist;
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('chatPrivilegesRevoked'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not error when chat privileges are revoked when sending a message to a party', async () => {
|
it('errors when chat privileges are revoked when sending a message to a party', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Party',
|
name: 'Party',
|
||||||
@@ -106,9 +109,12 @@ describe('POST /chat', () => {
|
|||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = await privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
|
await expect(privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage }))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
expect(message.message.id).to.exist;
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('chatPrivilegesRevoked'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,7 +129,7 @@ describe('POST /chat', () => {
|
|||||||
member.updateOne({ 'flags.chatShadowMuted': false });
|
member.updateOne({ 'flags.chatShadowMuted': false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
it('creates a chat with flagCount set when sending a message to a private guild', async () => {
|
||||||
await member.updateOne({
|
await member.updateOne({
|
||||||
'flags.chatShadowMuted': true,
|
'flags.chatShadowMuted': true,
|
||||||
});
|
});
|
||||||
@@ -131,10 +137,10 @@ describe('POST /chat', () => {
|
|||||||
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a chat with zero flagCount when sending a message to a party', async () => {
|
it('creates a chat with flagCount set when sending a message to a party', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Party',
|
name: 'Party',
|
||||||
@@ -153,7 +159,7 @@ describe('POST /chat', () => {
|
|||||||
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,6 +244,18 @@ describe('POST /chat', () => {
|
|||||||
expect(groupMessages[0].id).to.exist;
|
expect(groupMessages[0].id).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('creates a chat with case-insensitive mentions', async () => {
|
||||||
|
const originalUsername = member.auth.local.username;
|
||||||
|
const uppercaseUsername = originalUsername.toUpperCase();
|
||||||
|
const messageWithMentions = `hi @${uppercaseUsername}`;
|
||||||
|
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
|
||||||
|
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||||
|
|
||||||
|
expect(newMessage.message.id).to.exist;
|
||||||
|
expect(newMessage.message.text).to.include(`[@${uppercaseUsername}](/profile/${member._id})`);
|
||||||
|
expect(groupMessages[0].id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a chat with a max length of 3000 chars', async () => {
|
it('creates a chat with a max length of 3000 chars', async () => {
|
||||||
const veryLongMessage = `
|
const veryLongMessage = `
|
||||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
requester,
|
requester,
|
||||||
translate as t,
|
translate as t,
|
||||||
|
generateUser,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import i18n from '../../../../../website/common/script/i18n';
|
import i18n from '../../../../../website/common/script/i18n';
|
||||||
|
|
||||||
@@ -56,4 +57,28 @@ describe('GET /content', () => {
|
|||||||
const res = await requester().get('/content?filter=backgroundsFlat,invalid');
|
const res = await requester().get('/content?filter=backgroundsFlat,invalid');
|
||||||
expect(res).to.not.have.property('backgroundsFlat');
|
expect(res).to.not.have.property('backgroundsFlat');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('authenticated user', () => {
|
||||||
|
let user;
|
||||||
|
it('returns content in user\'s preferred language when no language parameter is provided', async () => {
|
||||||
|
user = await generateUser({ 'preferences.language': 'de' });
|
||||||
|
const res = await user.get('/content');
|
||||||
|
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
|
||||||
|
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'de'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects language parameter over user\'s preferred language', async () => {
|
||||||
|
user = await generateUser({ 'preferences.language': 'de' });
|
||||||
|
const res = await user.get('/content?language=fr');
|
||||||
|
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
|
||||||
|
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'fr'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to English if user\'s preferred language is invalid', async () => {
|
||||||
|
user = await generateUser({ 'preferences.language': 'invalid_lang' });
|
||||||
|
const res = await user.get('/content');
|
||||||
|
expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach');
|
||||||
|
expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
|
||||||
import {
|
|
||||||
generateUser,
|
|
||||||
translate as t,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
|
||||||
|
|
||||||
xdescribe('GET /export/avatar-:memberId.html', () => {
|
|
||||||
let user;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
user = await generateUser();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('validates req.params.memberId', async () => {
|
|
||||||
await expect(user.get('/export/avatar-:memberId.html')).to.eventually.be.rejected.and.eql({
|
|
||||||
code: 400,
|
|
||||||
error: 'BadRequest',
|
|
||||||
message: t('invalidReqParams'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles non-existing members', async () => {
|
|
||||||
const dummyId = generateUUID();
|
|
||||||
await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({
|
|
||||||
code: 404,
|
|
||||||
error: 'NotFound',
|
|
||||||
message: t('userWithIDNotFound', { userId: dummyId }),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an html page', async () => {
|
|
||||||
const res = await user.get(`/export/avatar-${user._id}.html`);
|
|
||||||
expect(res.substring(0, 100).indexOf('<!DOCTYPE html>')).to.equal(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// TODO how to test this route since it points to a file on AWS s3?
|
|
||||||
|
|
||||||
describe('GET /export/avatar-:memberId.png', () => {});
|
|
||||||
@@ -38,7 +38,7 @@ describe('GET /export/inbox.html', () => {
|
|||||||
it('renders the markdown messages as html', async () => {
|
it('renders the markdown messages as html', async () => {
|
||||||
const res = await user.get('/export/inbox.html');
|
const res = await user.get('/export/inbox.html');
|
||||||
|
|
||||||
expect(res).to.include('img class="habitica-emoji"');
|
expect(res).to.include('😄');
|
||||||
expect(res).to.include('<h1>Hello!</h1>');
|
expect(res).to.include('<h1>Hello!</h1>');
|
||||||
expect(res).to.include('<li>list 1</li>');
|
expect(res).to.include('<li>list 1</li>');
|
||||||
});
|
});
|
||||||
@@ -46,7 +46,7 @@ describe('GET /export/inbox.html', () => {
|
|||||||
it('sorts messages from newest to oldest', async () => {
|
it('sorts messages from newest to oldest', async () => {
|
||||||
const res = await user.get('/export/inbox.html');
|
const res = await user.get('/export/inbox.html');
|
||||||
|
|
||||||
const emojiPosition = res.indexOf('img class="habitica-emoji"');
|
const emojiPosition = res.indexOf('😄');
|
||||||
const headingPosition = res.indexOf('<h1>Hello!</h1>');
|
const headingPosition = res.indexOf('<h1>Hello!</h1>');
|
||||||
const listPosition = res.indexOf('<li>list 1</li>');
|
const listPosition = res.indexOf('<li>list 1</li>');
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,24 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fakes sending an invite if user is shadow muted', async () => {
|
||||||
|
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||||
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
|
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
|
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||||
|
});
|
||||||
|
expect(response).to.be.an('Array');
|
||||||
|
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||||
|
expect(response[0]._id).to.be.a('String');
|
||||||
|
expect(response[0].id).to.eql(group._id);
|
||||||
|
expect(response[0].name).to.eql(groupName);
|
||||||
|
expect(response[0].inviter).to.eql(inviter._id);
|
||||||
|
|
||||||
|
await expect(userToInvite.get('/user'))
|
||||||
|
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
|
||||||
|
});
|
||||||
|
|
||||||
it('invites a user to a group by username', async () => {
|
it('invites a user to a group by username', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
@@ -209,6 +227,24 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fakes sending an invite if user is shadow muted', async () => {
|
||||||
|
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||||
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
|
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
|
uuids: [userToInvite._id],
|
||||||
|
});
|
||||||
|
expect(response).to.be.an('Array');
|
||||||
|
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||||
|
expect(response[0]._id).to.be.a('String');
|
||||||
|
expect(response[0].id).to.eql(group._id);
|
||||||
|
expect(response[0].name).to.eql(groupName);
|
||||||
|
expect(response[0].inviter).to.eql(inviter._id);
|
||||||
|
|
||||||
|
await expect(userToInvite.get('/user'))
|
||||||
|
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
|
||||||
|
});
|
||||||
|
|
||||||
it('invites a user to a group by uuid', async () => {
|
it('invites a user to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
@@ -281,6 +317,19 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fakes sending invite when inviter is shadow muted', async () => {
|
||||||
|
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||||
|
const res = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
|
emails: [testInvite],
|
||||||
|
inviter: 'inviter name',
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedUser = await inviterMuted.sync();
|
||||||
|
|
||||||
|
expect(res).to.exist;
|
||||||
|
expect(updatedUser.invitesSent).to.eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns an error when invite is missing an email', async () => {
|
it('returns an error when invite is missing an email', async () => {
|
||||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||||
emails: [{ name: 'test' }],
|
emails: [{ name: 'test' }],
|
||||||
@@ -405,6 +454,19 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fakes sending an invite if user is shadow muted', async () => {
|
||||||
|
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
|
||||||
|
const newUser = await generateUser();
|
||||||
|
const invite = await inviterMuted.post(`/groups/${group._id}/invite`, {
|
||||||
|
uuids: [newUser._id],
|
||||||
|
emails: [{ name: 'test', email: 'test@habitica.com' }],
|
||||||
|
});
|
||||||
|
const invitedUser = await newUser.get('/user');
|
||||||
|
|
||||||
|
expect(invitedUser.invitations.parties[0]).to.not.exist;
|
||||||
|
expect(invite).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
it('invites users to a group by uuid and email', async () => {
|
it('invites users to a group by uuid and email', async () => {
|
||||||
const newUser = await generateUser();
|
const newUser = await generateUser();
|
||||||
const invite = await inviter.post(`/groups/${group._id}/invite`, {
|
const invite = await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ describe('GET /inbox/messages', () => {
|
|||||||
it('returns four messages when using page-query ', async () => {
|
it('returns four messages when using page-query ', async () => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let i = 0; i < 10; i += 1) {
|
for (let i = 0; i < 50; i += 1) {
|
||||||
promises.push(user.post('/members/send-private-message', {
|
promises.push(user.post('/members/send-private-message', {
|
||||||
toUserId: user.id,
|
toUserId: user.id,
|
||||||
message: 'fourth',
|
message: 'fourth',
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when to user has blocked the sender', async () => {
|
it('returns error when recipient has blocked the sender', async () => {
|
||||||
const receiver = await generateUser({ 'inbox.blocks': [userToSendMessage._id] });
|
const receiver = await generateUser({ 'inbox.blocks': [userToSendMessage._id] });
|
||||||
|
|
||||||
await expect(userToSendMessage.post('/members/send-private-message', {
|
await expect(userToSendMessage.post('/members/send-private-message', {
|
||||||
@@ -56,7 +56,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when sender has blocked to user', async () => {
|
it('returns error when sender has blocked recipient', async () => {
|
||||||
const receiver = await generateUser();
|
const receiver = await generateUser();
|
||||||
const sender = await generateUser({ 'inbox.blocks': [receiver._id] });
|
const sender = await generateUser({ 'inbox.blocks': [receiver._id] });
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when to user has opted out of messaging', async () => {
|
it('returns error when recipient has opted out of messaging', async () => {
|
||||||
const receiver = await generateUser({ 'inbox.optOut': true });
|
const receiver = await generateUser({ 'inbox.optOut': true });
|
||||||
|
|
||||||
await expect(userToSendMessage.post('/members/send-private-message', {
|
await expect(userToSendMessage.post('/members/send-private-message', {
|
||||||
@@ -174,7 +174,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows admin to send when sender has blocked the admin', async () => {
|
it('allows admin to send when recipient has blocked the admin', async () => {
|
||||||
userToSendMessage = await generateUser({
|
userToSendMessage = await generateUser({
|
||||||
'permissions.moderator': true,
|
'permissions.moderator': true,
|
||||||
});
|
});
|
||||||
@@ -202,7 +202,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
expect(sendersMessageInSendersInbox).to.exist;
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows admin to send when to user has opted out of messaging', async () => {
|
it('allows admin to send when recipient has opted out of messaging', async () => {
|
||||||
userToSendMessage = await generateUser({
|
userToSendMessage = await generateUser({
|
||||||
'permissions.moderator': true,
|
'permissions.moderator': true,
|
||||||
});
|
});
|
||||||
@@ -229,4 +229,58 @@ describe('POST /members/send-private-message', () => {
|
|||||||
expect(sendersMessageInReceiversInbox).to.exist;
|
expect(sendersMessageInReceiversInbox).to.exist;
|
||||||
expect(sendersMessageInSendersInbox).to.exist;
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sender is shadow muted', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
userToSendMessage = await generateUser({
|
||||||
|
'flags.chatShadowMuted': true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not save the message in the receiver inbox', async () => {
|
||||||
|
const receiver = await generateUser();
|
||||||
|
|
||||||
|
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.message.uuid).to.equal(receiver._id);
|
||||||
|
|
||||||
|
const updatedReceiver = await receiver.get('/user');
|
||||||
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
|
const sendersMessageInReceiversInbox = _.find(
|
||||||
|
updatedReceiver.inbox.messages,
|
||||||
|
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
|
||||||
|
);
|
||||||
|
|
||||||
|
const sendersMessageInSendersInbox = _.find(
|
||||||
|
updatedSender.inbox.messages,
|
||||||
|
message => message.uuid === receiver._id && message.text === messageToSend,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sendersMessageInReceiversInbox).to.not.exist;
|
||||||
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not save the message message twice if recipient is sender', async () => {
|
||||||
|
const response = await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: userToSendMessage._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.message.uuid).to.equal(userToSendMessage._id);
|
||||||
|
|
||||||
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
|
const sendersMessageInSendersInbox = _.find(
|
||||||
|
updatedSender.inbox.messages,
|
||||||
|
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
|
expect(Object.keys(updatedSender.inbox.messages).length).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -91,6 +91,23 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
|||||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('heals stuck RSVPNeeded when group already has the user accepted', async () => {
|
||||||
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||||
|
|
||||||
|
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
|
||||||
|
await partyMembers[0].sync();
|
||||||
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
|
||||||
|
|
||||||
|
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||||
|
expect(res).to.exist;
|
||||||
|
|
||||||
|
await partyMembers[0].sync();
|
||||||
|
await questingGroup.sync();
|
||||||
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
|
||||||
|
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not accept invite for a quest already underway', async () => {
|
it('does not accept invite for a quest already underway', async () => {
|
||||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||||
|
|||||||
@@ -100,6 +100,23 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
|||||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('heals stuck RSVPNeeded when group already has the user rejected', async () => {
|
||||||
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
|
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
|
||||||
|
|
||||||
|
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
|
||||||
|
await partyMembers[0].sync();
|
||||||
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
|
||||||
|
|
||||||
|
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
|
||||||
|
expect(res).to.exist;
|
||||||
|
|
||||||
|
await partyMembers[0].sync();
|
||||||
|
await questingGroup.sync();
|
||||||
|
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
|
||||||
|
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('return an error when a user rejects an invite already accepted', async () => {
|
it('return an error when a user rejects an invite already accepted', async () => {
|
||||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
|
||||||
|
|
||||||
describe('POST /user/sleep', () => {
|
describe('POST /user/sleep', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -23,15 +22,4 @@ describe('POST /user/sleep', () => {
|
|||||||
await user.sync();
|
await user.sync();
|
||||||
expect(user.preferences.sleep).to.be.false;
|
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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('GET /user/auth/apple', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('registers a new user', async () => {
|
it('registers a new user', async () => {
|
||||||
const response = await api.get(appleEndpoint);
|
const response = await api.get(`${appleEndpoint}?allowRegister=true`);
|
||||||
|
|
||||||
expect(response.apiToken).to.exist;
|
expect(response.apiToken).to.exist;
|
||||||
expect(response.id).to.exist;
|
expect(response.id).to.exist;
|
||||||
@@ -35,7 +35,7 @@ describe('GET /user/auth/apple', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('logs an existing user in', async () => {
|
it('logs an existing user in', async () => {
|
||||||
const registerResponse = await api.get(appleEndpoint);
|
const registerResponse = await api.get(`${appleEndpoint}?allowRegister=true`);
|
||||||
|
|
||||||
const response = await api.get(appleEndpoint);
|
const response = await api.get(appleEndpoint);
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,28 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
|
|||||||
expect(isPassValid).to.equal(true);
|
expect(isPassValid).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('changes the apiToken on password reset', async () => {
|
||||||
|
const user = await generateUser();
|
||||||
|
const previousToken = user.apiToken;
|
||||||
|
|
||||||
|
const code = encrypt(JSON.stringify({
|
||||||
|
userId: user._id,
|
||||||
|
expiresAt: moment().add({ days: 1 }),
|
||||||
|
}));
|
||||||
|
await user.updateOne({
|
||||||
|
'auth.local.passwordResetCode': code,
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.post(`${endpoint}`, {
|
||||||
|
newPassword: 'my new password',
|
||||||
|
confirmPassword: 'my new password',
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.apiToken).to.not.eql(previousToken);
|
||||||
|
});
|
||||||
|
|
||||||
it('renders the success page and convert the password from sha1 to bcrypt', async () => {
|
it('renders the success page and convert the password from sha1 to bcrypt', async () => {
|
||||||
const user = await generateUser();
|
const user = await generateUser();
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('POST /user/auth/local/login', () => {
|
|||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id, username: user.auth.local.username }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,6 +110,18 @@ describe('POST /user/auth/local/login', () => {
|
|||||||
expect(isValidPassword).to.equal(true);
|
expect(isValidPassword).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets auth.timestamps.updated', async () => {
|
||||||
|
const oldUpdated = new Date(user.auth.timestamps.updated);
|
||||||
|
// login
|
||||||
|
await api.post(endpoint, {
|
||||||
|
username: user.auth.local.email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.timestamps.updated).to.be.greaterThan(oldUpdated);
|
||||||
|
});
|
||||||
|
|
||||||
it('user uses social authentication and has no password', async () => {
|
it('user uses social authentication and has no password', async () => {
|
||||||
await user.unset({
|
await user.unset({
|
||||||
'auth.local.hashed_password': 1,
|
'auth.local.hashed_password': 1,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
|
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
|
||||||
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
||||||
|
import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent';
|
||||||
|
|
||||||
function generateRandomUserName () {
|
function generateRandomUserName () {
|
||||||
return (Date.now() + uuid()).substring(0, 20);
|
return (Date.now() + uuid()).substring(0, 20);
|
||||||
@@ -41,6 +42,25 @@ describe('POST /user/auth/local/register', () => {
|
|||||||
expect(user.newUser).to.eql(true);
|
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 () => {
|
it('registers a new user and sets verifiedUsername to true', async () => {
|
||||||
const username = generateRandomUserName();
|
const username = generateRandomUserName();
|
||||||
const email = `${username}@example.com`;
|
const email = `${username}@example.com`;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
translate as t,
|
translate as t,
|
||||||
getProperty,
|
getProperty,
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} 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', () => {
|
describe('POST /user/auth/social', () => {
|
||||||
let api;
|
let api;
|
||||||
@@ -64,6 +66,77 @@ describe('POST /user/auth/social', () => {
|
|||||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
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
|
||||||
|
network,
|
||||||
|
username: 'Google User Name',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.equal('GoogleUserName');
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.equal('googleusername');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a random username if provided username contains only disallowed characters', async () => {
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
username: 'Áîüè',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a random username if provided username contains a disallowed word', async () => {
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
username: 'i am a TESTPLACEHOLDERSLURWORDHERE',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a random username if sanitized username conflicts with an extant user', async () => {
|
||||||
|
user = await generateUser({ 'auth.local.username': 'GoogleUserName' });
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
username: 'Google User Name',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.username')).to.eventually.contain('hb-');
|
||||||
|
await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.eventually.contain('hb-');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if allowRegister is false and user does not exist', async () => {
|
||||||
|
await expect(api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
allowRegister: false,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: `${apiErrorMessages.socialFlowUserNotFound} ${user.auth.local.username}+google@example.com`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('logs an existing user in', async () => {
|
it('logs an existing user in', async () => {
|
||||||
const registerResponse = await api.post(endpoint, {
|
const registerResponse = await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -131,6 +204,36 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('logs an existing user into their social account if allowRegister is false', async () => {
|
||||||
|
const registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
expect(registerResponse.newUser).to.be.true;
|
||||||
|
// This is important for existing accounts before the new social handling
|
||||||
|
passport._strategies.google.userProfile.restore();
|
||||||
|
const expectedResult = {
|
||||||
|
id: randomGoogleId,
|
||||||
|
displayName: 'a google user',
|
||||||
|
emails: [
|
||||||
|
{ value: user.auth.local.email },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||||
|
|
||||||
|
const response = await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
allowRegister: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
|
expect(response.apiToken).not.to.eql(user.apiToken);
|
||||||
|
expect(response.id).not.to.eql(user._id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('add social auth to an existing user', async () => {
|
it('add social auth to an existing user', async () => {
|
||||||
const response = await user.post(endpoint, {
|
const response = await user.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -142,6 +245,17 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.newUser).to.be.false;
|
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 () => {
|
it('does not log into other account if social auth already exists', async () => {
|
||||||
const registerResponse = await api.post(endpoint, {
|
const registerResponse = await api.post(endpoint, {
|
||||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
@@ -167,5 +281,24 @@ describe('POST /user/auth/social', () => {
|
|||||||
|
|
||||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets auth.timestamps.updated', async () => {
|
||||||
|
let oldUpdated = new Date(user.auth.timestamps.updated);
|
||||||
|
await user.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.timestamps.updated).to.be.greaterThan(oldUpdated);
|
||||||
|
oldUpdated = new Date(user.auth.timestamps.updated);
|
||||||
|
|
||||||
|
// Do it again to ensure it updates even when nothing else changes
|
||||||
|
await api.post(endpoint, {
|
||||||
|
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.timestamps.updated).to.be.greaterThan(oldUpdated);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,11 +27,30 @@ describe('PUT /user/auth/update-password', async () => {
|
|||||||
newPassword,
|
newPassword,
|
||||||
confirmPassword: newPassword,
|
confirmPassword: newPassword,
|
||||||
});
|
});
|
||||||
expect(response).to.eql({});
|
|
||||||
|
expect(response).to.exist;
|
||||||
|
expect(response.apiToken).to.exist;
|
||||||
|
|
||||||
await user.sync();
|
await user.sync();
|
||||||
expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword);
|
expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should change the apiToken on password change', async () => {
|
||||||
|
const previousToken = user.apiToken;
|
||||||
|
const response = await user.put(ENDPOINT, {
|
||||||
|
password,
|
||||||
|
newPassword,
|
||||||
|
confirmPassword: newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newToken = response.apiToken;
|
||||||
|
expect(newToken).to.exist;
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.apiToken).to.eql(newToken);
|
||||||
|
expect(user.apiToken).to.not.eql(previousToken);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns an error when confirmPassword does not match newPassword', async () => {
|
it('returns an error when confirmPassword does not match newPassword', async () => {
|
||||||
await expect(user.put(ENDPOINT, {
|
await expect(user.put(ENDPOINT, {
|
||||||
password,
|
password,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe('GET /inbox/conversations', () => {
|
|||||||
it('returns five messages when using page-query ', async () => {
|
it('returns five messages when using page-query ', async () => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let i = 0; i < 10; i += 1) {
|
for (let i = 0; i < 50; i += 1) {
|
||||||
promises.push(user.post('/members/send-private-message', {
|
promises.push(user.post('/members/send-private-message', {
|
||||||
toUserId: user.id,
|
toUserId: user.id,
|
||||||
message: 'fourth',
|
message: 'fourth',
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
translate as t,
|
||||||
|
requester,
|
||||||
|
generateUser,
|
||||||
|
} from '../../../../helpers/api-integration/v4';
|
||||||
|
|
||||||
|
const ENDPOINT = '/user/auth/check-email';
|
||||||
|
|
||||||
|
describe('POST /user/auth/check-email', () => {
|
||||||
|
const email = 'SOmE-nEw-emAIl_2@example.net';
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
api = requester();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns email if it is not used yet', async () => {
|
||||||
|
const response = await api.post(ENDPOINT, {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
expect(response.email).to.eql(email);
|
||||||
|
expect(response.valid).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects if email is not provided', async () => {
|
||||||
|
await expect(api.post(ENDPOINT, {
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'Invalid request parameters.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects if email is already taken', async () => {
|
||||||
|
const user = await generateUser();
|
||||||
|
|
||||||
|
const response = await api.post(ENDPOINT, {
|
||||||
|
email: user.auth.local.email,
|
||||||
|
});
|
||||||
|
expect(response).to.eql({
|
||||||
|
valid: false,
|
||||||
|
email: user.auth.local.email,
|
||||||
|
error: t('cannotFulfillReq'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects if casing is different', async () => {
|
||||||
|
const user = await generateUser();
|
||||||
|
|
||||||
|
const response = await api.post(ENDPOINT, {
|
||||||
|
email: user.auth.local.email.toUpperCase(),
|
||||||
|
});
|
||||||
|
expect(response).to.eql({
|
||||||
|
valid: false,
|
||||||
|
email: user.auth.local.email.toUpperCase(),
|
||||||
|
error: t('cannotFulfillReq'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects if email uses restricted domain', async () => {
|
||||||
|
const response = await api.post(ENDPOINT, {
|
||||||
|
email: 'fake@habitica.com',
|
||||||
|
});
|
||||||
|
expect(response.valid).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import md from 'habitica-markdown';
|
||||||
|
|
||||||
|
describe('habiticaMarkdown emoji plugin', () => {
|
||||||
|
it('renders standard emoji as Unicode', () => {
|
||||||
|
const result = md.render(':smile:');
|
||||||
|
expect(result).to.include('😄');
|
||||||
|
expect(result).not.to.include('img');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders thumbsup emoji as Unicode', () => {
|
||||||
|
const result = md.render(':thumbsup:');
|
||||||
|
expect(result).to.include('👍');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders +1 emoji as Unicode', () => {
|
||||||
|
const result = md.render(':+1:');
|
||||||
|
expect(result).to.include('👍');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders melior as an img tag', () => {
|
||||||
|
const result = md.render(':melior:');
|
||||||
|
expect(result).to.include('<img class="habitica-emoji"');
|
||||||
|
expect(result).to.include('src="https://s3.amazonaws.com/habitica-assets/cdn/emoji/melior.png"');
|
||||||
|
expect(result).to.include('alt="melior"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT convert emoji inside markdown links', () => {
|
||||||
|
const result = md.render('[:smile: link](http://example.com)');
|
||||||
|
expect(result).to.include(':smile: link');
|
||||||
|
expect(result).not.to.include('😄');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts emoji outside of links normally', () => {
|
||||||
|
const result = md.render(':smile: [link](http://example.com)');
|
||||||
|
expect(result).to.include('😄');
|
||||||
|
expect(result).to.include('link');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves removed custom emoji (bowtie) as literal text', () => {
|
||||||
|
const result = md.render(':bowtie:');
|
||||||
|
expect(result).to.include(':bowtie:');
|
||||||
|
expect(result).not.to.include('img');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves unknown shortcodes as literal text', () => {
|
||||||
|
const result = md.render(':nonexistent_emoji_xyz:');
|
||||||
|
expect(result).to.include(':nonexistent_emoji_xyz:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders new emoji not in the old dataset', () => {
|
||||||
|
const result = md.render(':yawning_face:');
|
||||||
|
expect(result).to.include('🥱');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports unsafeHTMLRender', () => {
|
||||||
|
const result = md.unsafeHTMLRender('<b>bold</b> :smile:');
|
||||||
|
expect(result).to.include('<b>bold</b>');
|
||||||
|
expect(result).to.include('😄');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports renderWithMentions', () => {
|
||||||
|
const result = md.renderWithMentions(':smile: @testuser', { userName: 'testuser' });
|
||||||
|
expect(result).to.include('😄');
|
||||||
|
expect(result).to.include('at-text');
|
||||||
|
expect(result).to.include('at-highlight');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
|||||||
|
|
||||||
describe('shared.ops.buy', () => {
|
describe('shared.ops.buy', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = generateUser({
|
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 () => {
|
it('returns error when key is not provided', async () => {
|
||||||
@@ -51,10 +44,8 @@ describe('shared.ops.buy', () => {
|
|||||||
|
|
||||||
it('buys health potion', async () => {
|
it('buys health potion', async () => {
|
||||||
user.stats.hp = 30;
|
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(user.stats.hp).to.eql(45);
|
||||||
|
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds equipment to inventory', async () => {
|
it('adds equipment to inventory', async () => {
|
||||||
|
|||||||
@@ -29,10 +29,9 @@ describe('shared.ops.buyArmoire', () => {
|
|||||||
const YIELD_EQUIPMENT = 0.5;
|
const YIELD_EQUIPMENT = 0.5;
|
||||||
const YIELD_FOOD = 0.7;
|
const YIELD_FOOD = 0.7;
|
||||||
const YIELD_EXP = 0.9;
|
const YIELD_EXP = 0.9;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buyArmoire (_user, _req, _analytics) {
|
async function buyArmoire (_user, _req) {
|
||||||
const buyOp = new BuyArmoireOperation(_user, _req, _analytics);
|
const buyOp = new BuyArmoireOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
@@ -50,12 +49,10 @@ describe('shared.ops.buyArmoire', () => {
|
|||||||
user.items.food = {};
|
user.items.food = {};
|
||||||
|
|
||||||
sandbox.stub(randomValFns, 'trueRandom');
|
sandbox.stub(randomValFns, 'trueRandom');
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
randomValFns.trueRandom.restore();
|
randomValFns.trueRandom.restore();
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('failure conditions', () => {
|
context('failure conditions', () => {
|
||||||
@@ -147,7 +144,7 @@ describe('shared.ops.buyArmoire', () => {
|
|||||||
|
|
||||||
expect(_.size(user.items.gear.owned)).to.equal(2);
|
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);
|
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(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
|
||||||
expect(user.stats.gp).to.eql(100);
|
expect(user.stats.gp).to.eql(100);
|
||||||
expect(analytics.track).to.be.calledTwice;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../../helpers/common.helper';
|
} 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 { BuyGemOperation } from '../../../../website/common/script/ops/buy/buyGem';
|
||||||
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
|
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
|
||||||
|
|
||||||
async function buyGem (user, req, analytics) {
|
async function buyGem (user, req) {
|
||||||
const buyOp = new BuyGemOperation(user, req, analytics);
|
const buyOp = new BuyGemOperation(user, req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('shared.ops.buyGem', () => {
|
describe('shared.ops.buyGem', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
const goldPoints = 40;
|
const goldPoints = 40;
|
||||||
const gemsBought = 40;
|
const gemsBought = 40;
|
||||||
const userGemAmount = 10;
|
const userGemAmount = 10;
|
||||||
@@ -35,23 +33,16 @@ describe('shared.ops.buyGem', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Gems', () => {
|
context('Gems', () => {
|
||||||
it('purchases gems', async () => {
|
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(message).to.equal(i18n.t('plusGem', { count: 1 }));
|
||||||
expect(user.balance).to.equal(userGemAmount + 0.25);
|
expect(user.balance).to.equal(userGemAmount + 0.25);
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(1);
|
expect(user.purchased.plan.gemsBought).to.equal(1);
|
||||||
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
|
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 () => {
|
it('purchases gems with a different language than the default', async () => {
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ import i18n from '../../../../website/common/script/i18n';
|
|||||||
|
|
||||||
describe('shared.ops.buyHealthPotion', () => {
|
describe('shared.ops.buyHealthPotion', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buyHealthPotion (_user, _req, _analytics) {
|
async function buyHealthPotion (_user, _req) {
|
||||||
const buyOp = new BuyHealthPotionOperation(_user, _req, _analytics);
|
const buyOp = new BuyHealthPotionOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
@@ -32,19 +31,13 @@ describe('shared.ops.buyHealthPotion', () => {
|
|||||||
},
|
},
|
||||||
stats: { gp: 200 },
|
stats: { gp: 200 },
|
||||||
});
|
});
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Potion', () => {
|
context('Potion', () => {
|
||||||
it('recovers 15 hp', async () => {
|
it('recovers 15 hp', async () => {
|
||||||
user.stats.hp = 30;
|
user.stats.hp = 30;
|
||||||
await buyHealthPotion(user, {}, analytics);
|
await buyHealthPotion(user, {});
|
||||||
expect(user.stats.hp).to.eql(45);
|
expect(user.stats.hp).to.eql(45);
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increase hp above 50', async () => {
|
it('does not increase hp above 50', async () => {
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ import {
|
|||||||
import i18n from '../../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
|
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
|
||||||
|
|
||||||
async function buyGear (user, req, analytics) {
|
async function buyGear (user, req) {
|
||||||
const buyOp = new BuyMarketGearOperation(user, req, analytics);
|
const buyOp = new BuyMarketGearOperation(user, req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('shared.ops.buyMarketGear', () => {
|
describe('shared.ops.buyMarketGear', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
let clock;
|
let clock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -47,14 +46,12 @@ describe('shared.ops.buyMarketGear', () => {
|
|||||||
sinon.stub(shared, 'randomVal');
|
sinon.stub(shared, 'randomVal');
|
||||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||||
sinon.stub(shared.fns, 'predictableRandom');
|
sinon.stub(shared.fns, 'predictableRandom');
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
shared.randomVal.restore();
|
shared.randomVal.restore();
|
||||||
shared.fns.predictableRandom.restore();
|
shared.fns.predictableRandom.restore();
|
||||||
shared.onboarding.checkOnboardingStatus.restore();
|
shared.onboarding.checkOnboardingStatus.restore();
|
||||||
analytics.track.restore();
|
|
||||||
|
|
||||||
if (clock) {
|
if (clock) {
|
||||||
clock.restore();
|
clock.restore();
|
||||||
@@ -65,7 +62,7 @@ describe('shared.ops.buyMarketGear', () => {
|
|||||||
it('adds equipment to inventory', async () => {
|
it('adds equipment to inventory', async () => {
|
||||||
user.stats.gp = 31;
|
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({
|
expect(user.items.gear.owned).to.eql({
|
||||||
weapon_warrior_0: true,
|
weapon_warrior_0: true,
|
||||||
@@ -92,13 +89,12 @@ describe('shared.ops.buyMarketGear', () => {
|
|||||||
eyewear_special_whiteHalfMoon: true,
|
eyewear_special_whiteHalfMoon: true,
|
||||||
eyewear_special_yellowHalfMoon: 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 () => {
|
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
|
||||||
user.stats.gp = 31;
|
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.calledOnce;
|
||||||
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
|
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
|
||||||
@@ -111,7 +107,7 @@ describe('shared.ops.buyMarketGear', () => {
|
|||||||
user.stats.gp = 31;
|
user.stats.gp = 31;
|
||||||
user.achievements.purchasedEquipment = true;
|
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;
|
expect(user.addAchievement).to.not.be.called;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
|||||||
|
|
||||||
describe('shared.ops.buyMysterySet', () => {
|
describe('shared.ops.buyMysterySet', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
let clock;
|
let clock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -27,11 +26,9 @@ describe('shared.ops.buyMysterySet', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
analytics.track.restore();
|
|
||||||
if (clock) {
|
if (clock) {
|
||||||
clock.restore();
|
clock.restore();
|
||||||
}
|
}
|
||||||
@@ -93,7 +90,7 @@ describe('shared.ops.buyMysterySet', () => {
|
|||||||
context('successful purchases', () => {
|
context('successful purchases', () => {
|
||||||
it('buys Steampunk Accessories Set', async () => {
|
it('buys Steampunk Accessories Set', async () => {
|
||||||
user.purchased.plan.consecutive.trinkets = 1;
|
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.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
|
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 () => {
|
it('buys mystery set if it is available', async () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
||||||
user.purchased.plan.consecutive.trinkets = 1;
|
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.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||||
expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true);
|
expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true);
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ describe('shared.ops.buyQuestGems', () => {
|
|||||||
let user;
|
let user;
|
||||||
let clock;
|
let clock;
|
||||||
const goldPoints = 40;
|
const goldPoints = 40;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buyQuest (_user, _req, _analytics) {
|
async function buyQuest (_user, _req) {
|
||||||
const buyOp = new BuyQuestWithGemOperation(_user, _req, _analytics);
|
const buyOp = new BuyQuestWithGemOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
@@ -25,13 +24,11 @@ describe('shared.ops.buyQuestGems', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
||||||
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
analytics.track.restore();
|
|
||||||
pinnedGearUtils.removeItemByPath.restore();
|
pinnedGearUtils.removeItemByPath.restore();
|
||||||
clock.restore();
|
clock.restore();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,21 +12,15 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
|||||||
|
|
||||||
describe('shared.ops.buyQuest', () => {
|
describe('shared.ops.buyQuest', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buyQuest (_user, _req, _analytics) {
|
async function buyQuest (_user, _req) {
|
||||||
const buyOp = new BuyQuestWithGoldOperation(_user, _req, _analytics);
|
const buyOp = new BuyQuestWithGoldOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = generateUser();
|
user = generateUser();
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('buys a Quest scroll', async () => {
|
it('buys a Quest scroll', async () => {
|
||||||
@@ -35,12 +29,11 @@ describe('shared.ops.buyQuest', () => {
|
|||||||
params: {
|
params: {
|
||||||
key: 'dilatoryDistress1',
|
key: 'dilatoryDistress1',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
expect(user.items.quests).to.eql({
|
expect(user.items.quests).to.eql({
|
||||||
dilatoryDistress1: 1,
|
dilatoryDistress1: 1,
|
||||||
});
|
});
|
||||||
expect(user.stats.gp).to.equal(5);
|
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 () => {
|
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;
|
user.items.quests[key] = -1;
|
||||||
await buyQuest(user, {
|
await buyQuest(user, {
|
||||||
params: { key },
|
params: { key },
|
||||||
}, analytics);
|
});
|
||||||
expect(user.items.quests[key]).to.equal(1);
|
expect(user.items.quests[key]).to.equal(1);
|
||||||
expect(user.stats.gp).to.equal(5);
|
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 () => {
|
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: {
|
params: {
|
||||||
key: 'dilatoryDistress1',
|
key: 'dilatoryDistress1',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
await buyQuest(user, {
|
await buyQuest(user, {
|
||||||
params: {
|
params: {
|
||||||
key: 'dilatoryDistress1',
|
key: 'dilatoryDistress1',
|
||||||
},
|
},
|
||||||
quantity: '3',
|
quantity: '3',
|
||||||
}, analytics);
|
});
|
||||||
|
|
||||||
expect(user.items.quests).to.eql({
|
expect(user.items.quests).to.eql({
|
||||||
dilatoryDistress1: 4,
|
dilatoryDistress1: 4,
|
||||||
@@ -82,7 +74,7 @@ describe('shared.ops.buyQuest', () => {
|
|||||||
key: 'dilatoryDistress1',
|
key: 'dilatoryDistress1',
|
||||||
},
|
},
|
||||||
quantity: 'a',
|
quantity: 'a',
|
||||||
}, analytics);
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err).to.be.an.instanceof(BadRequest);
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||||
@@ -187,12 +179,11 @@ describe('shared.ops.buyQuest', () => {
|
|||||||
params: {
|
params: {
|
||||||
key: 'dilatoryDistress3',
|
key: 'dilatoryDistress3',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
|
|
||||||
expect(user.items.quests).to.eql({
|
expect(user.items.quests).to.eql({
|
||||||
dilatoryDistress3: 1,
|
dilatoryDistress3: 1,
|
||||||
});
|
});
|
||||||
expect(user.stats.gp).to.equal(100);
|
expect(user.stats.gp).to.equal(100);
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,20 +14,17 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
|||||||
describe('shared.ops.buySpecialSpell', () => {
|
describe('shared.ops.buySpecialSpell', () => {
|
||||||
let user;
|
let user;
|
||||||
let clock;
|
let clock;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buySpecialSpell (_user, _req, _analytics) {
|
async function buySpecialSpell (_user, _req) {
|
||||||
const buyOp = new BuySpellOperation(_user, _req, _analytics);
|
const buyOp = new BuySpellOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = generateUser();
|
user = generateUser();
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
analytics.track.restore();
|
|
||||||
if (clock) {
|
if (clock) {
|
||||||
clock.restore();
|
clock.restore();
|
||||||
}
|
}
|
||||||
@@ -78,7 +75,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
|||||||
params: {
|
params: {
|
||||||
key: 'thankyou',
|
key: 'thankyou',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
|
|
||||||
expect(user.stats.gp).to.equal(1);
|
expect(user.stats.gp).to.equal(1);
|
||||||
expect(user.items.special.thankyou).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', {
|
expect(message).to.equal(i18n.t('messageBought', {
|
||||||
itemText: item.text(),
|
itemText: item.text(),
|
||||||
}));
|
}));
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('buys a limited card when it is available', async () => {
|
it('buys a limited card when it is available', async () => {
|
||||||
@@ -101,7 +97,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
|||||||
params: {
|
params: {
|
||||||
key: 'nye',
|
key: 'nye',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
|
|
||||||
expect(user.stats.gp).to.equal(1);
|
expect(user.stats.gp).to.equal(1);
|
||||||
expect(user.items.special.nye).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', {
|
expect(message).to.equal(i18n.t('messageBought', {
|
||||||
itemText: item.text(),
|
itemText: item.text(),
|
||||||
}));
|
}));
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if the card is not currently available', async () => {
|
it('throws an error if the card is not currently available', async () => {
|
||||||
@@ -140,7 +135,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
|||||||
params: {
|
params: {
|
||||||
key: 'seafoam',
|
key: 'seafoam',
|
||||||
},
|
},
|
||||||
}, analytics);
|
});
|
||||||
|
|
||||||
expect(user.stats.gp).to.equal(1);
|
expect(user.stats.gp).to.equal(1);
|
||||||
expect(user.items.special.seafoam).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', {
|
expect(message).to.equal(i18n.t('messageBought', {
|
||||||
itemText: item.text(),
|
itemText: item.text(),
|
||||||
}));
|
}));
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if the spell is not currently available', async () => {
|
it('throws an error if the spell is not currently available', async () => {
|
||||||
|
|||||||
@@ -13,21 +13,15 @@ import { BuyHourglassMountOperation } from '../../../../website/common/script/op
|
|||||||
|
|
||||||
describe('common.ops.hourglassPurchase', () => {
|
describe('common.ops.hourglassPurchase', () => {
|
||||||
let user;
|
let user;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
async function buyMount (_user, _req, _analytics) {
|
async function buyMount (_user, _req) {
|
||||||
const buyOp = new BuyHourglassMountOperation(_user, _req, _analytics);
|
const buyOp = new BuyHourglassMountOperation(_user, _req);
|
||||||
|
|
||||||
return buyOp.purchase();
|
return buyOp.purchase();
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = generateUser();
|
user = generateUser();
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
analytics.track.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('failure conditions', () => {
|
context('failure conditions', () => {
|
||||||
@@ -131,12 +125,11 @@ describe('common.ops.hourglassPurchase', () => {
|
|||||||
it('buys a pet', async () => {
|
it('buys a pet', async () => {
|
||||||
user.purchased.plan.consecutive.trinkets = 2;
|
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(message).to.eql(i18n.t('hourglassPurchase'));
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||||
expect(user.items.pets).to.eql({ 'MantisShrimp-Base': 5 });
|
expect(user.items.pets).to.eql({ 'MantisShrimp-Base': 5 });
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('buys a mount', async () => {
|
it('buys a mount', async () => {
|
||||||
|
|||||||
@@ -17,20 +17,17 @@ describe('shared.ops.purchase', () => {
|
|||||||
let user;
|
let user;
|
||||||
let clock;
|
let clock;
|
||||||
const goldPoints = 40;
|
const goldPoints = 40;
|
||||||
const analytics = { track () {} };
|
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
user = generateUser({ 'stats.class': 'rogue' });
|
user = generateUser({ 'stats.class': 'rogue' });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(analytics, 'track');
|
|
||||||
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
||||||
clock = sandbox.useFakeTimers(new Date('2024-01-10'));
|
clock = sandbox.useFakeTimers(new Date('2024-01-10'));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
analytics.track.restore();
|
|
||||||
pinnedGearUtils.removeItemByPath.restore();
|
pinnedGearUtils.removeItemByPath.restore();
|
||||||
clock.restore();
|
clock.restore();
|
||||||
});
|
});
|
||||||
@@ -187,11 +184,10 @@ describe('shared.ops.purchase', () => {
|
|||||||
const type = 'eggs';
|
const type = 'eggs';
|
||||||
const key = 'Wolf';
|
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(user.items[type][key]).to.equal(1);
|
||||||
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
|
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
|
||||||
expect(analytics.track).to.be.calledOnce;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('purchases hatchingPotions', async () => {
|
it('purchases hatchingPotions', async () => {
|
||||||
@@ -332,7 +328,7 @@ describe('shared.ops.purchase', () => {
|
|||||||
const key = 'Wolf';
|
const key = 'Wolf';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
|
await purchase(user, { params: { type, key }, quantity: 'jamboree' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err).to.be.an.instanceof(BadRequest);
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||||
@@ -345,7 +341,7 @@ describe('shared.ops.purchase', () => {
|
|||||||
user.balance = 10;
|
user.balance = 10;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
|
await purchase(user, { params: { type, key }, quantity: -2 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err).to.be.an.instanceof(BadRequest);
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||||
@@ -358,7 +354,7 @@ describe('shared.ops.purchase', () => {
|
|||||||
user.balance = 10;
|
user.balance = 10;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
|
await purchase(user, { params: { type, key }, quantity: 2.9 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err).to.be.an.instanceof(BadRequest);
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||||
|
|||||||
@@ -211,22 +211,32 @@ describe('shared.ops.rebirth', () => {
|
|||||||
expect(user.achievements.rebirthLevel).to.equal(2);
|
expect(user.achievements.rebirthLevel).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not increment rebirth achievements when level is lower than previous', async () => {
|
it('increments rebirth achievements even when level is lower than previous', async () => {
|
||||||
user.stats.lvl = 2;
|
user.stats.lvl = 2;
|
||||||
user.achievements.rebirths = 1;
|
user.achievements.rebirths = 1;
|
||||||
user.achievements.rebirthLevel = 3;
|
user.achievements.rebirthLevel = 3;
|
||||||
|
|
||||||
await rebirth(user);
|
await rebirth(user);
|
||||||
|
|
||||||
expect(user.achievements.rebirths).to.equal(1);
|
expect(user.achievements.rebirths).to.equal(2);
|
||||||
expect(user.achievements.rebirthLevel).to.equal(3);
|
expect(user.achievements.rebirthLevel).to.equal(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('always increments rebirth achievements when level is MAX_LEVEL', async () => {
|
it('updates rebirthLevel when current level is higher than previous', async () => {
|
||||||
|
user.stats.lvl = 5;
|
||||||
|
user.achievements.rebirths = 1;
|
||||||
|
user.achievements.rebirthLevel = 3;
|
||||||
|
|
||||||
|
await rebirth(user);
|
||||||
|
|
||||||
|
expect(user.achievements.rebirths).to.equal(2);
|
||||||
|
expect(user.achievements.rebirthLevel).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments rebirth achievements when level is MAX_LEVEL', async () => {
|
||||||
user.stats.lvl = MAX_LEVEL;
|
user.stats.lvl = MAX_LEVEL;
|
||||||
user.achievements.rebirths = 1;
|
user.achievements.rebirths = 1;
|
||||||
// this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test
|
user.achievements.rebirthLevel = MAX_LEVEL;
|
||||||
user.achievements.rebirthLevel = MAX_LEVEL + 1;
|
|
||||||
|
|
||||||
await rebirth(user);
|
await rebirth(user);
|
||||||
|
|
||||||
@@ -234,11 +244,10 @@ describe('shared.ops.rebirth', () => {
|
|||||||
expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL);
|
expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('always increments rebirth achievements when level is greater than MAX_LEVEL', async () => {
|
it('increments rebirth achievements when level is greater than MAX_LEVEL', async () => {
|
||||||
user.stats.lvl = MAX_LEVEL + 1;
|
user.stats.lvl = MAX_LEVEL + 1;
|
||||||
user.achievements.rebirths = 1;
|
user.achievements.rebirths = 1;
|
||||||
// this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test
|
user.achievements.rebirthLevel = MAX_LEVEL;
|
||||||
user.achievements.rebirthLevel = MAX_LEVEL + 2;
|
|
||||||
|
|
||||||
await rebirth(user);
|
await rebirth(user);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ describe('shared.ops.unlock', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = generateUser();
|
user = generateUser();
|
||||||
user.balance = usersStartingGems;
|
user.balance = usersStartingGems;
|
||||||
|
user.pinnedItems.push({ type: 'background', path: 'backgrounds.backgrounds042016.giant_florals' });
|
||||||
|
user.pinnedItems.push({ type: 'haircolor', path: 'hair.color.rainbow' });
|
||||||
|
user.pinnedItems.push({ type: 'shirt', path: 'shirt.convict' });
|
||||||
clock = sandbox.useFakeTimers(new Date('2024-04-10'));
|
clock = sandbox.useFakeTimers(new Date('2024-04-10'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,6 +275,7 @@ describe('shared.ops.unlock', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('unlocks an item (appearance)', async () => {
|
it('unlocks an item (appearance)', async () => {
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'shirt')).to.not.equal(-1);
|
||||||
const path = unlockPath.split(',')[0];
|
const path = unlockPath.split(',')[0];
|
||||||
const initialShirts = Object.keys(user.purchased.shirt).length;
|
const initialShirts = Object.keys(user.purchased.shirt).length;
|
||||||
const [, message] = await unlock(user, { query: { path } });
|
const [, message] = await unlock(user, { query: { path } });
|
||||||
@@ -282,11 +286,12 @@ describe('shared.ops.unlock', () => {
|
|||||||
);
|
);
|
||||||
expect(get(user.purchased, path)).to.be.true;
|
expect(get(user.purchased, path)).to.be.true;
|
||||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'shirt')).to.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unlocks an item (hair color)', async () => {
|
it('unlocks an item (hair color)', async () => {
|
||||||
user.purchased.hair.color = {};
|
user.purchased.hair.color = {};
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'haircolor')).to.not.equal(-1);
|
||||||
const path = hairUnlockPath.split(',')[0];
|
const path = hairUnlockPath.split(',')[0];
|
||||||
const initialColorHair = Object.keys(user.purchased.hair.color).length;
|
const initialColorHair = Object.keys(user.purchased.hair.color).length;
|
||||||
const [, message] = await unlock(user, { query: { path } });
|
const [, message] = await unlock(user, { query: { path } });
|
||||||
@@ -297,6 +302,7 @@ describe('shared.ops.unlock', () => {
|
|||||||
);
|
);
|
||||||
expect(get(user.purchased, path)).to.be.true;
|
expect(get(user.purchased, path)).to.be.true;
|
||||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'haircolor')).to.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unlocks an item (facial hair)', async () => {
|
it('unlocks an item (facial hair)', async () => {
|
||||||
@@ -334,6 +340,7 @@ describe('shared.ops.unlock', () => {
|
|||||||
|
|
||||||
it('unlocks an item (background)', async () => {
|
it('unlocks an item (background)', async () => {
|
||||||
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'background')).to.not.equal(-1);
|
||||||
const [, message] = await unlock(user, {
|
const [, message] = await unlock(user, {
|
||||||
query: { path: backgroundUnlockPath },
|
query: { path: backgroundUnlockPath },
|
||||||
});
|
});
|
||||||
@@ -344,6 +351,7 @@ describe('shared.ops.unlock', () => {
|
|||||||
);
|
);
|
||||||
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
|
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
|
||||||
expect(user.balance).to.equal(usersStartingGems - 1.75);
|
expect(user.balance).to.equal(usersStartingGems - 1.75);
|
||||||
|
expect(user.pinnedItems.findIndex(item => item.type === 'background')).to.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles an invalid hair path gracefully', async () => {
|
it('handles an invalid hair path gracefully', async () => {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { getMatchingSwap, makeSubstitutionMap } from '../../website/common/script/content/constants/aprilFools';
|
||||||
|
|
||||||
|
describe('April Fools', () => {
|
||||||
|
describe('getMatchingSwap', () => {
|
||||||
|
it('returns Veggie for 2020', () => {
|
||||||
|
const swap = getMatchingSwap(new Date('2020-04-01'));
|
||||||
|
expect(swap).to.equal('Veggie');
|
||||||
|
});
|
||||||
|
it('returns Alien for 2026', () => {
|
||||||
|
const swap = getMatchingSwap(new Date('2026-04-01'));
|
||||||
|
expect(swap).to.equal('Alien');
|
||||||
|
});
|
||||||
|
it('Cycles through swaps correctly', () => {
|
||||||
|
const swap = getMatchingSwap(new Date('2027-04-01'));
|
||||||
|
expect(swap).to.equal('Veggie');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('makeSubstitutionMap', () => {
|
||||||
|
it('returns correct substitution for Veggie', () => {
|
||||||
|
const substitutions = makeSubstitutionMap('Veggie');
|
||||||
|
expect(substitutions.pets['Pet-Wolf-']).to.equal('Pet-Wolf-Veggie');
|
||||||
|
expect(substitutions.pets['Pet-TigerCub-']).to.equal('Pet-TigerCub-Veggie');
|
||||||
|
expect(substitutions.pets['Pet-Yarn-']).to.equal('Pet-BearCub-Veggie');
|
||||||
|
expect(substitutions.pets.default).to.equal('Pet-Dragon-Veggie');
|
||||||
|
expect(substitutions.pets.noPet).to.equal('Pet-Wolf-Veggie');
|
||||||
|
expect(substitutions.pets.noPetIOS).to.equal('Pet-TigerCub-Veggie');
|
||||||
|
expect(substitutions.pets.noPetAndroid).to.equal('Pet-Cactus-Veggie');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct substitution for Cryptid', () => {
|
||||||
|
const substitutions = makeSubstitutionMap('Cryptid');
|
||||||
|
expect(substitutions.pets['Pet-Fox-']).to.equal('Pet-Fox-Cryptid');
|
||||||
|
expect(substitutions.pets['Pet-FlyingPig-']).to.equal('Pet-FlyingPig-Cryptid');
|
||||||
|
expect(substitutions.pets['Pet-Yarn-']).to.equal('Pet-BearCub-Cryptid');
|
||||||
|
expect(substitutions.pets.default).to.equal('Pet-Dragon-Cryptid');
|
||||||
|
expect(substitutions.pets.noPet).to.equal('Pet-Wolf-Cryptid');
|
||||||
|
expect(substitutions.pets.noPetAndroid).to.equal('Pet-Cactus-Cryptid');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -54,19 +54,4 @@ describe('armoire', () => {
|
|||||||
const febuaryItems = armoire.all;
|
const febuaryItems = armoire.all;
|
||||||
expect(febuaryItems.length).to.equal(384);
|
expect(febuaryItems.length).to.equal(384);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets have at least 2 items', () => {
|
|
||||||
const setMap = {};
|
|
||||||
forEach(armoire.all, item => {
|
|
||||||
// Gotta have one outlier
|
|
||||||
if (!item.set || item.set.startsWith('armoire-')) return;
|
|
||||||
if (setMap[item.set] === undefined) {
|
|
||||||
setMap[item.set] = 0;
|
|
||||||
}
|
|
||||||
setMap[item.set] += 1;
|
|
||||||
});
|
|
||||||
Object.keys(setMap).forEach(set => {
|
|
||||||
expect(setMap[set], set).to.be.at.least(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -133,21 +133,21 @@ describe('Content Schedule', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sets the end date for a gala', () => {
|
it('sets the end date for a gala', () => {
|
||||||
const date = new Date('2024-05-20');
|
const date = new Date('2024-05-31');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2024-06-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the end date for a winter gala', () => {
|
it('sets the end date for a winter gala', () => {
|
||||||
const date = new Date('2024-12-22');
|
const date = new Date('2025-02-28');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the end date in new year for a winter gala', () => {
|
it('sets the end date in new year for a winter gala', () => {
|
||||||
const date = new Date('2025-01-04');
|
const date = new Date('2025-02-28');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-01T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses correct date for first hours of the month', () => {
|
it('uses correct date for first hours of the month', () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe('Shop Featured Items', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('contains the current premium hatching potions', () => {
|
it('contains the current premium hatching potions', () => {
|
||||||
clock = Sinon.useFakeTimers(new Date('2024-04-08'));
|
clock = Sinon.useFakeTimers(new Date('2024-04-09'));
|
||||||
const items = featuredItems.market();
|
const items = featuredItems.market();
|
||||||
expect(_.find(items, item => item.path === 'premiumHatchingPotions.Porcelain')).to.exist;
|
expect(_.find(items, item => item.path === 'premiumHatchingPotions.Porcelain')).to.exist;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { STRING_ERROR_MSG, STRING_DOES_NOT_EXIST_MSG } from '../helpers/content.helper';
|
import { STRING_DOES_NOT_EXIST_MSG } from '../helpers/content.helper';
|
||||||
import translator from '../../website/common/script/content/translation';
|
import translator from '../../website/common/script/content/translation';
|
||||||
|
|
||||||
describe('Translator', () => {
|
describe('Translator', () => {
|
||||||
it('returns error message if string is not properly formatted', () => {
|
|
||||||
const improperlyFormattedString = translator('petName', { attr: 0 })();
|
|
||||||
expect(improperlyFormattedString).to.match(STRING_ERROR_MSG);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an error message if string does not exist', () => {
|
it('returns an error message if string does not exist', () => {
|
||||||
const stringDoesNotExist = translator('stringDoesNotExist')();
|
const stringDoesNotExist = translator('stringDoesNotExist')();
|
||||||
expect(stringDoesNotExist).to.match(STRING_DOES_NOT_EXIST_MSG);
|
expect(stringDoesNotExist).to.match(STRING_DOES_NOT_EXIST_MSG);
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ function _requestMaker (user, method, additionalSets = {}) {
|
|||||||
|| route.indexOf('/paypal') === 0
|
|| route.indexOf('/paypal') === 0
|
||||||
|| route.indexOf('/amazon') === 0
|
|| route.indexOf('/amazon') === 0
|
||||||
|| route.indexOf('/stripe') === 0
|
|| route.indexOf('/stripe') === 0
|
||||||
|| route.indexOf('/analytics') === 0
|
|
||||||
) {
|
) {
|
||||||
url += `${route}`;
|
url += `${route}`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import i18n from '../../website/common/script/i18n';
|
import i18n from '../../website/common/script/i18n';
|
||||||
import './globals.helper';
|
import './globals.helper';
|
||||||
import { translations } from '../../website/server/libs/i18n';
|
import { contentTranslations } from '../../website/server/libs/i18n';
|
||||||
|
|
||||||
i18n.translations = translations;
|
i18n.translations = contentTranslations;
|
||||||
|
|
||||||
export const STRING_ERROR_MSG = /^Error processing the string ".*". Please see Help > Report a Bug.$/;
|
export const STRING_ERROR_MSG = /^Error processing the string ".*". Please see Help > Report a Bug.$/;
|
||||||
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ const sinonStubPromise = require('sinon-stub-promise');
|
|||||||
sinonStubPromise(global.sinon);
|
sinonStubPromise(global.sinon);
|
||||||
global.sandbox = sinon.createSandbox();
|
global.sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
const setupNconf = require('../../website/server/libs/setupNconf').default;
|
||||||
|
|
||||||
setupNconf('./config.json.example');
|
setupNconf('./config.json.example');
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export async function getProperty (collectionName, id, path) {
|
|||||||
// Specifically helpful for the GET /groups tests,
|
// Specifically helpful for the GET /groups tests,
|
||||||
// resets the db to an empty state and creates a tavern document
|
// resets the db to an empty state and creates a tavern document
|
||||||
export async function resetHabiticaDB () {
|
export async function resetHabiticaDB () {
|
||||||
|
console.info('Resetting Habitica DB');
|
||||||
const groups = mongoose.connection.db.collection('groups');
|
const groups = mongoose.connection.db.collection('groups');
|
||||||
const users = mongoose.connection.db.collection('users');
|
const users = mongoose.connection.db.collection('users');
|
||||||
return mongoose.connection.dropDatabase()
|
return mongoose.connection.dropDatabase()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
const setupNconf = require('../../website/server/libs/setupNconf').default;
|
||||||
|
|
||||||
// fix further imports of require/import syntaxes
|
// fix further imports of require/import syntaxes
|
||||||
require('@babel/register');
|
require('@babel/register');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
es2021: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/vue',
|
'habitrpg/lib/vue',
|
||||||
@@ -11,20 +12,12 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
// TODO find a way to let eslint understand webpack aliases
|
|
||||||
'import/no-unresolved': 'off',
|
'import/no-unresolved': 'off',
|
||||||
'import/no-extraneous-dependencies': 'off',
|
'import/no-extraneous-dependencies': 'off',
|
||||||
'import/extensions': 'off',
|
'import/extensions': 'off',
|
||||||
'prefer-regex-literals': 'warn',
|
'prefer-regex-literals': 'warn',
|
||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off',
|
||||||
'vue/no-mutating-props': 'warn',
|
'vue/no-mutating-props': 'warn',
|
||||||
// this creates issues with the current way we have to push the process.env vars to webpack
|
|
||||||
// https://github.com/eslint/eslint/issues/14918
|
|
||||||
// https://github.com/webpack/webpack/issues/5392
|
|
||||||
// off for now, because any eslint --fix will then still do it anyway
|
|
||||||
// maybe this can be turned on again once we switch to newer vue/vite
|
|
||||||
// Important! process.env.XYZ should not be destructured
|
|
||||||
'prefer-destructuring': 'off',
|
|
||||||
'vue/html-self-closing': ['error', {
|
'vue/html-self-closing': ['error', {
|
||||||
html: {
|
html: {
|
||||||
void: 'never',
|
void: 'never',
|
||||||
@@ -39,7 +32,4 @@ module.exports = {
|
|||||||
order: ['template', 'style', 'script'],
|
order: ['template', 'style', 'script'],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Habitica - FAQ</title>
|
||||||
|
<meta name="description" content="Frequently Asked Questions about Habitica, the gamified task manager.">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,400i,700,700i|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||||
|
<link rel="shortcut icon" sizes="48x48" href="/static/icons/favicon.ico">
|
||||||
|
<link rel="shortcut icon" sizes="192x192" href="/static/icons/favicon_192x192.png">
|
||||||
|
<link rel="mask-icon" href="/static/icons/favicon.ico">
|
||||||
|
<meta property="og:image" content="/static/emails/images/meta-image.png" />
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
|
||||||
|
<!-- Translations -->
|
||||||
|
<script type='text/javascript' src='/api/v4/i18n/core' vite-ignore></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<link rel="shortcut icon" sizes="192x192" href="/static/icons/favicon_192x192.png">
|
<link rel="shortcut icon" sizes="192x192" href="/static/icons/favicon_192x192.png">
|
||||||
<link rel="mask-icon" href="/static/icons/favicon.ico">
|
<link rel="mask-icon" href="/static/icons/favicon.ico">
|
||||||
<meta property="og:image" content="/static/emails/images/meta-image.png" />
|
<meta property="og:image" content="/static/emails/images/meta-image.png" />
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loading-screen">
|
<div id="loading-screen">
|
||||||
@@ -28,10 +29,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
|
|
||||||
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
|
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
|
||||||
<!-- Translations -->
|
<!-- Translations -->
|
||||||
<script type='text/javascript' src='/api/v4/i18n/browser-script'></script>
|
<script type='text/javascript' src='/api/v4/i18n/core' vite-ignore></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Generated
+6729
-10136
File diff suppressed because it is too large
Load Diff
+21
-21
@@ -3,62 +3,62 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vite",
|
||||||
"build": "vue-cli-service build",
|
"serve:docker": "npx vite --host 0.0.0.0",
|
||||||
"test:unit": "vue-cli-service test:unit --require ./tests/unit/helpers.js",
|
"build": "vite build",
|
||||||
"lint": "vue-cli-service lint .",
|
"preview": "vite preview",
|
||||||
"lint-no-fix": "vue-cli-service lint --no-fix .",
|
"test:unit": "vitest run",
|
||||||
|
"test:unit:watch": "vitest watch",
|
||||||
|
"lint": "eslint --ext .js,.vue --ignore-path ../../.gitignore --fix .",
|
||||||
|
"lint-no-fix": "eslint --ext .js,.vue --no-fix src",
|
||||||
"postinstall": "node ./scripts/npm-postinstall.js"
|
"postinstall": "node ./scripts/npm-postinstall.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/cli-plugin-babel": "^5.0.8",
|
"@froxz/vite-plugin-s3": "^1.6.0",
|
||||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
"@vitejs/plugin-vue2": "^2.3.3",
|
||||||
"@vue/cli-plugin-router": "^5.0.8",
|
|
||||||
"@vue/cli-plugin-unit-mocha": "^5.0.8",
|
|
||||||
"@vue/cli-service": "^5.0.8",
|
|
||||||
"@vue/test-utils": "1.0.0-beta.29",
|
"@vue/test-utils": "1.0.0-beta.29",
|
||||||
"amplitude-js": "^8.21.3",
|
"amplitude-js": "^8.21.3",
|
||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^0.28.0",
|
"axios": "^0.28.0",
|
||||||
"axios-progress-bar": "^1.2.0",
|
"axios-progress-bar": "^1.2.0",
|
||||||
"babel-eslint": "^10.1.0",
|
|
||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"bootstrap-vue": "^2.23.1",
|
"bootstrap-vue": "^2.23.1",
|
||||||
"core-js": "^3.33.1",
|
|
||||||
"eslint": "7.32.0",
|
"eslint": "7.32.0",
|
||||||
"eslint-config-habitrpg": "6.2.0",
|
"eslint-config-habitrpg": "6.2.0",
|
||||||
"eslint-plugin-mocha": "5.3.0",
|
"eslint-plugin-mocha": "5.3.0",
|
||||||
"eslint-plugin-vue": "7.20.0",
|
"eslint-plugin-vue": "7.20.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^4.0.0",
|
||||||
"hellojs": "^1.20.0",
|
"hellojs": "^1.20.0",
|
||||||
"intro.js": "^7.2.0",
|
"intro.js": "^7.2.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"markdown-it": "^14.0.0",
|
||||||
|
"micromustache": "^8.0.3",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-locales-webpack-plugin": "^1.2.0",
|
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"sass": "^1.63.4",
|
"sass": "^1.63.4",
|
||||||
"sass-loader": "^14.1.1",
|
|
||||||
"sinon": "^17.0.1",
|
"sinon": "^17.0.1",
|
||||||
"stopword": "^2.0.8",
|
"stopword": "^2.0.8",
|
||||||
"timers-browserify": "^2.0.12",
|
"timers-browserify": "^2.0.12",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"validator": "^13.9.0",
|
"validator": "^13.9.0",
|
||||||
|
"vite": "^6.3.6",
|
||||||
|
"vite-plugin-compression2": "^1.3.3",
|
||||||
"vue": "^2.7.10",
|
"vue": "^2.7.10",
|
||||||
"vue-fragment": "^1.6.0",
|
|
||||||
"vue-mugen-scroll": "^0.2.6",
|
"vue-mugen-scroll": "^0.2.6",
|
||||||
"vue-router": "^3.6.5",
|
"vue-router": "^3.6.5",
|
||||||
"vue-template-babel-compiler": "^2.0.0",
|
|
||||||
"vue-template-compiler": "^2.7.10",
|
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0"
|
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
||||||
|
"@vitest/browser": "^3.0.5",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"chai": "^5.1.0",
|
|
||||||
"inspectpack": "^4.7.1",
|
"inspectpack": "^4.7.1",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"jsdom": "^26.0.0",
|
||||||
"webpack": "^5.94.0"
|
"mocha": "^11.1.0",
|
||||||
|
"playwright": "^1.50.1",
|
||||||
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-24
@@ -29,12 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<snackbars />
|
<snackbars />
|
||||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||||
<user-main v-else />
|
<div v-else>
|
||||||
|
<user-main />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '@/assets/scss/colors.scss';
|
||||||
|
|
||||||
#loading-screen-inapp {
|
#loading-screen-inapp {
|
||||||
#melior {
|
#melior {
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '@/assets/scss/colors.scss';
|
||||||
|
|
||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
opacity: .9 !important;
|
opacity: .9 !important;
|
||||||
@@ -106,18 +108,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import * as Analytics from '@/libs/analytics';
|
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userMain from '@/pages/user-main';
|
|
||||||
import snackbars from '@/components/snackbars/notifications';
|
import snackbars from '@/components/snackbars/notifications';
|
||||||
|
import { LOCALSTORAGE_AUTH_KEY } from '@/libs/auth';
|
||||||
|
|
||||||
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
|
const COMMUNITY_MANAGER_EMAIL = import.meta.env.EMAILS_COMMUNITY_MANAGER_EMAIL;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
snackbars,
|
snackbars,
|
||||||
userMain,
|
userMain: () => import('@/pages/user-main'),
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -148,10 +149,6 @@ export default {
|
|||||||
this.hideLoadingScreen();
|
this.hideLoadingScreen();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.$nextTick(() => {
|
|
||||||
// Load external scripts after the app has been rendered
|
|
||||||
Analytics.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
axios.interceptors.response.use(response => { // Set up Response interceptors
|
axios.interceptors.response.use(response => { // Set up Response interceptors
|
||||||
// Verify that the user was not updated from another browser/app/client
|
// Verify that the user was not updated from another browser/app/client
|
||||||
@@ -206,26 +203,45 @@ export default {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}, error => { // Set up Error interceptors
|
}, error => { // Set up Error interceptors
|
||||||
|
if (!error.response) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
if (error.response.status >= 400) {
|
if (error.response.status >= 400) {
|
||||||
const isBanned = this.checkForBannedUser(error);
|
const isBanned = this.checkForBannedUser(error);
|
||||||
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
||||||
|
|
||||||
// Don't show errors from getting user details. These users have delete their account,
|
// Don't show errors from getting user details. These users have deleted their account,
|
||||||
// but their chat message still exists.
|
// but their chat message still exists.
|
||||||
const configExists = Boolean(error.response) && Boolean(error.response.config);
|
const configExists = Boolean(error.response) && Boolean(error.response.config);
|
||||||
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) {
|
if (configExists) {
|
||||||
// @TODO: We resolve the promise because we need our caching to cache this user as tried
|
if (error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) {
|
||||||
// Chat paging should help this, but maybe we can also find another solution..
|
// @TODO: We resolve the promise because we need our caching to cache this user as tried
|
||||||
return Promise.resolve(error);
|
// Chat paging should help this, but maybe we can also find another solution..
|
||||||
|
return Promise.resolve(error);
|
||||||
|
}
|
||||||
|
// Also, a 404 occurs during routine attempt to log in with social,
|
||||||
|
// when we check for account already existing.
|
||||||
|
if (error.response.config.method === 'post' && (error.response.config.url.indexOf('/api/v4/user/auth/social') !== -1
|
||||||
|
|| error.response.config.url.indexOf('/api/v4/user/auth/apple') !== -1)) {
|
||||||
|
const socialEmail = error.response.data.message.split(': ')[1];
|
||||||
|
if (socialEmail) {
|
||||||
|
window.sessionStorage.setItem('social-email', socialEmail);
|
||||||
|
}
|
||||||
|
return Promise.resolve(error);
|
||||||
|
}
|
||||||
|
if (error.response.status === 404
|
||||||
|
&& error.response.config.method === 'get'
|
||||||
|
&& error.response.config.url.indexOf('/api/v4/groups/party') !== -1) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorData = error.response.data;
|
const errorData = error.response.data;
|
||||||
const errorMessage = errorData.message || errorData;
|
const errorMessage = errorData.message || errorData;
|
||||||
|
const errorCode = errorData.error;
|
||||||
|
|
||||||
// Check for conditions to reset the user auth
|
// If 'invalid_credentials' signaled, force logout
|
||||||
// TODO use a specific error like NotificationNotFound instead of checking for the string
|
if (error.response.status === 401 && errorCode === 'invalid_credentials') {
|
||||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
|
||||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
|
||||||
this.$store.dispatch('auth:logout', { redirectToLogin: true });
|
this.$store.dispatch('auth:logout', { redirectToLogin: true });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -268,16 +284,29 @@ export default {
|
|||||||
const loadingScreen = document.getElementById('loading-screen');
|
const loadingScreen = document.getElementById('loading-screen');
|
||||||
if (loadingScreen) document.body.removeChild(loadingScreen);
|
if (loadingScreen) document.body.removeChild(loadingScreen);
|
||||||
|
|
||||||
if (this.isStaticPage || !this.isUserLoggedIn) {
|
// Check if we need to show password change success message
|
||||||
this.hideLoadingScreen();
|
if (sessionStorage.getItem('passwordChangeSuccess') === 'true') {
|
||||||
|
sessionStorage.removeItem('passwordChangeSuccess');
|
||||||
|
this.$store.dispatch('snackbars:add', {
|
||||||
|
title: 'Habitica',
|
||||||
|
text: this.$t('passwordSuccess'),
|
||||||
|
type: 'success',
|
||||||
|
timeout: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$router.onReady(() => {
|
||||||
|
if (this.isStaticPage || !this.isUserLoggedIn) {
|
||||||
|
this.hideLoadingScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hideLoadingScreen () {
|
hideLoadingScreen () {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
checkForBannedUser (error) {
|
checkForBannedUser (error) {
|
||||||
const AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
|
||||||
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
||||||
const errorMessage = error.response.data.message;
|
const errorMessage = error.response.data.message;
|
||||||
|
|
||||||
@@ -301,4 +330,3 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="@/assets/scss/index.scss" lang="scss"></style>
|
<style src="@/assets/scss/index.scss" lang="scss"></style>
|
||||||
<style src="@/assets/scss/sprites.scss" lang="scss"></style>
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user