diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 79f806a5e63..56dba30aeb0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -22,7 +22,8 @@ services: dockerfile: ./Dockerfile-Dev command: ["npm", "start"] depends_on: - - mongo + mongo: + condition: service_healthy environment: - NODE_DB_URI=mongodb://mongo/habitrpg networks: @@ -33,7 +34,16 @@ services: - .:/usr/src/habitica - /usr/src/habitica/node_modules mongo: - image: mongo:3.6 + 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: diff --git a/habitica-images b/habitica-images index 88511f3603b..dfb04339a43 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 88511f3603b41da9d1dccd39350dbb58bc90d61a +Subproject commit dfb04339a4351aecc17325b9b4e011bef72a0dc3 diff --git a/migrations/archive/2024/2024_purge_invite_accepted.js b/migrations/archive/2024/20240806_purge_invite_accepted.js similarity index 100% rename from migrations/archive/2024/2024_purge_invite_accepted.js rename to migrations/archive/2024/20240806_purge_invite_accepted.js diff --git a/migrations/archive/2024/20241119_gem_caps_hourglasses.js b/migrations/archive/2024/20241119_gem_caps_hourglasses.js new file mode 100644 index 00000000000..81682b99f66 --- /dev/null +++ b/migrations/archive/2024/20241119_gem_caps_hourglasses.js @@ -0,0 +1,115 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20241119_gem_caps_hourglasses'; +import { model as User } from '../../../website/server/models/user'; + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + const { consecutive, customerId, dateTerminated, planId } = user.purchased.plan; + const isRecurring = customerId !== 'Gift' && !dateTerminated; + const updateOp = { + $set: { + migration: MIGRATION_NAME, + 'purchased.plan.consecutive.gemCapExtra': Math.max(2 * Math.ceil((consecutive.gemCapExtra + 1) / 2, 26)), + }, + $inc: {}, + }; + + let hourglassBonus = 0; + + if (isRecurring) { + await user.updateBalance( + 5, + 'admin_update_balance', + '', + 'Subscription Reward Migration', + ); + updateOp.$inc.balance = 5; + switch (planId) { + case 'basic': + case 'basic_earned': + case 'group_plan_auto': + hourglassBonus = 2; + break; + case 'basic_3mo': + case 'basic_6mo': + case 'google_6mo': + hourglassBonus = 4; + break; + case 'basic_12mo': + hourglassBonus = 12; + updateOp.$set['purchased.plan.hourglassPromoReceived'] = new Date(); + break; + default: + hourglassBonus = 0; + } + + if (hourglassBonus) { + updateOp.$inc['purchased.plan.consecutive.trinkets'] = hourglassBonus; + await user.updateHourglasses( + hourglassBonus, + 'admin_update_balance', + '', + 'Subscription Reward Migration', + ); + } + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_subscriber_reward', + title: 'Thanks for being a subscriber!', + text: 'Enjoy these extra Mystic Hourglasses and Gems to celebrate our new benefits.', + }, + seen: false, + }, + }; + } + + return await User.updateOne( + { _id: user._id }, + updateOp, + ).exec(); +} + +export default async function processUsers () { + let query = { + migration: { $ne: MIGRATION_NAME }, + 'purchased.plan.customerId': { $exists: true }, + $or: [ + { 'purchased.plan.dateTerminated': { $exists: false } }, + { 'purchased.plan.dateTerminated': null }, + { 'purchased.plan.dateTerminated': { $gt: new Date() } }, + ], + }; + + const fields = { + _id: 1, + purchased: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/users/habitoween.js b/migrations/users/habitoween.js new file mode 100644 index 00000000000..fbf33993637 --- /dev/null +++ b/migrations/users/habitoween.js @@ -0,0 +1,175 @@ +/* + * Award Habitoween ladder items to participants in this month's Habitoween festivities + */ +/* eslint-disable no-console */ +import { model as User } from '../../website/server/models/user'; + +const MIGRATION_NAME = '20241030_habitoween_ladder'; // Update when running in future years + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const set = { migration: MIGRATION_NAME }; + const inc = { + 'items.food.Candy_Skeleton': 1, + 'items.food.Candy_Base': 1, + 'items.food.Candy_CottonCandyBlue': 1, + 'items.food.Candy_CottonCandyPink': 1, + 'items.food.Candy_Shade': 1, + 'items.food.Candy_White': 1, + 'items.food.Candy_Golden': 1, + 'items.food.Candy_Zombie': 1, + 'items.food.Candy_Desert': 1, + 'items.food.Candy_Red': 1, + }; + const push = { notifications: { $each: [] } }; + + if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-RoyalPurple']) { + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_candy', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-RoyalPurple']) { + set['items.mounts.JackOLantern-RoyalPurple'] = true; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_purple_mount', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Royal Purple Jack-O-Lantern Mount and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Glow']) { + set['items.pets.JackOLantern-RoyalPurple'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_purple_pet', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Royal Purple Jack-O-Lantern Pet and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Glow']) { + set['items.mounts.JackOLantern-Glow'] = true; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_glow_mount', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Glow-in-the-Dark Jack-O-Lantern Mount and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Ghost']) { + set['items.pets.JackOLantern-Glow'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_glow_pet', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Glow-in-the-Dark Jack-O-Lantern Pet and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) { + set['items.mounts.JackOLantern-Ghost'] = true; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_ghost_mount', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Ghost Jack-O-Lantern Mount and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) { + set['items.pets.JackOLantern-Ghost'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_ghost_pet', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Ghost Jack-O-Lantern Pet and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) { + set['items.mounts.JackOLantern-Base'] = true; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_base_mount', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Jack-O-Lantern Mount and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } else { + set['items.pets.JackOLantern-Base'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_habitoween_base_pet', + title: 'Happy Habitoween!', + text: 'For this spooky celebration, you\'ve received a Jack-O-Lantern Pet and an assortment of candy for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }); + } + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + return User.updateOne({ _id: user._id }, { $inc: inc, $push: push, $set: set }).exec(); +} + +export default async function processUsers () { + const query = { + migration: { $ne: MIGRATION_NAME }, + 'auth.timestamps.loggedin': { $gt: new Date('2024-10-01') }, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({ _id: 1 }) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +} diff --git a/migrations/users/harvest_feast.js b/migrations/users/harvest_feast.js new file mode 100644 index 00000000000..dfa2d3431ab --- /dev/null +++ b/migrations/users/harvest_feast.js @@ -0,0 +1,167 @@ +/* eslint-disable no-console */ +import { model as User } from '../../website/server/models/user'; + +const MIGRATION_NAME = '20241120_harvest_feast'; +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const updateOp = { + $set: { migration: MIGRATION_NAME }, + }; + + if (typeof user.items.gear.owned.head_special_turkeyHelmGilded !== 'undefined') { + updateOp.$inc = { + 'items.food.Pie_Base': 1, + 'items.food.Pie_CottonCandyBlue': 1, + 'items.food.Pie_CottonCandyPink': 1, + 'items.food.Pie_Desert': 1, + 'items.food.Pie_Golden': 1, + 'items.food.Pie_Red': 1, + 'items.food.Pie_Shade': 1, + 'items.food.Pie_Skeleton': 1, + 'items.food.Pie_Zombie': 1, + 'items.food.Pie_White': 1, + }; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_pie', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received an assortment of pie for your Pets!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } else if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_turkeyHelmGilded'] = true; + updateOp.$set['items.gear.owned.armor_special_turkeyArmorGilded'] = true; + updateOp.$set['items.gear.owned.back_special_turkeyTailGilded'] = true; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_gilded_set', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Gilded Turkey Armor, Helm, and Tail!', + destination: '/inventory/equipment', + }, + seen: false, + }, + }; + } else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) { + updateOp.$set['items.gear.owned.head_special_turkeyHelmBase'] = true; + updateOp.$set['items.gear.owned.armor_special_turkeyArmorBase'] = true; + updateOp.$set['items.gear.owned.back_special_turkeyTailBase'] = true; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_base_set', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Turkey Armor, Helm, and Tail!', + destination: '/inventory/equipment', + }, + seen: false, + }, + }; + } else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) { + updateOp.$set['items.mounts.Turkey-Gilded'] = true; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_gilded_mount', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Gilded Turkey Mount!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) { + updateOp.$set['items.pets.Turkey-Gilded'] = 5; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_gilded_pet', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Gilded Turkey Pet!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) { + updateOp.$set['items.mounts.Turkey-Base'] = true; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_base_mount', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Turkey Mount!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } else { + updateOp.$set['items.pets.Turkey-Base'] = 5; + updateOp.$push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_harvestfeast_base_pet', + title: 'Happy Harvest Feast!', + text: 'Gobble gobble, you\'ve received the Turkey Pet!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return User.updateOne({ _id: user._id }, updateOp).exec(); +} + +export default async function processUsers () { + const query = { + migration: { $ne: MIGRATION_NAME }, + 'auth.timestamps.loggedin': { $gt: new Date('2024-10-20') }, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({ _id: 1 }) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +} diff --git a/migrations/users/nye.js b/migrations/users/nye.js new file mode 100644 index 00000000000..89ee34ac531 --- /dev/null +++ b/migrations/users/nye.js @@ -0,0 +1,125 @@ +/* eslint-disable no-console */ +import { model as User } from '../../website/server/models/user'; + +const MIGRATION_NAME = '20231228_nye'; +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const updateOp = { + $set: { migration: MIGRATION_NAME }, + $push: { }, + }; + const data = { + title: 'Happy New Year!', + destination: 'inventory/equipment', + }; + + if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') { + updateOp.$inc = { + 'items.food.Candy_Skeleton': 1, + 'items.food.Candy_Base': 1, + 'items.food.Candy_CottonCandyBlue': 1, + 'items.food.Candy_CottonCandyPink': 1, + 'items.food.Candy_Shade': 1, + 'items.food.Candy_White': 1, + 'items.food.Candy_Golden': 1, + 'items.food.Candy_Zombie': 1, + 'items.food.Candy_Desert': 1, + 'items.food.Candy_Red': 1, + }; + data.icon = 'notif_candy_nye'; + data.text = 'You’ve received an assortment of candy to celebrate with your Pets!'; + data.destination = '/inventory/stable'; + } else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2023'] = true; + data.icon = 'notif_2023hat_nye'; + data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2022'] = true; + data.icon = 'notif_2022hat_nye'; + data.text = 'Take on your resolutions with style in this Fabulous Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2021'] = true; + data.icon = 'notif_2021hat_nye'; + data.text = 'Take on your resolutions with style in this Preposterous Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2020'] = true; + data.icon = 'notif_2020hat_nye'; + data.text = 'Take on your resolutions with style in this Extravagant Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2019'] = true; + data.icon = 'notif_2019hat_nye'; + data.text = 'Take on your resolutions with style in this Outrageous Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2018'] = true; + data.icon = 'notif_2018hat_nye'; + data.text = 'Take on your resolutions with style in this Outlandish Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2017'] = true; + data.icon = 'notif_2017hat_nye'; + data.text = 'Take on your resolutions with style in this Fanciful Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2016'] = true; + data.icon = 'notif_2016hat_nye'; + data.text = 'Take on your resolutions with style in this Whimsical Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2015'] = true; + data.icon = 'notif_2015hat_nye'; + data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!'; + } else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') { + updateOp.$set['items.gear.owned.head_special_nye2014'] = true; + data.icon = 'notif_2014hat_nye'; + data.text = 'Take on your resolutions with style in this Silly Party Hat!'; + } else { + updateOp.$set['items.gear.owned.head_special_nye'] = true; + data.icon = 'notif_2013hat_nye'; + data.text = 'Take on your resolutions with style in this Absurd Party Hat!'; + } + + updateOp.$push.notifications = { + type: 'ITEM_RECEIVED', + data, + seen: false, + }; + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return User.updateOne({ _id: user._id }, updateOp).exec(); +} + +export default async function processUsers () { + const query = { + 'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') }, + migration: { $ne: MIGRATION_NAME }, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({ _id: 1 }) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +} diff --git a/package-lock.json b/package-lock.json index d1cf5c53d35..f4b6f0699bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "habitica", - "version": "5.28.6", + "version": "5.32.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "habitica", - "version": "5.28.6", + "version": "5.32.0", "hasInstallScript": true, "dependencies": { "@babel/core": "^7.22.10", @@ -33,7 +33,7 @@ "eslint": "^8.55.0", "eslint-config-habitrpg": "^6.2.3", "eslint-plugin-mocha": "^5.0.0", - "express": "^4.21.0", + "express": "^4.21.1", "express-basic-auth": "^1.2.1", "express-validator": "^5.2.0", "firebase-admin": "^12.1.1", @@ -56,7 +56,7 @@ "method-override": "^3.0.0", "moment": "^2.29.4", "moment-recur": "^1.0.7", - "mongoose": "^7.6.3", + "mongoose": "^7.8.3", "morgan": "^1.10.0", "nconf": "^0.12.1", "node-gcm": "^1.0.5", @@ -92,7 +92,7 @@ "chai-as-promised": "^7.1.1", "chai-moment": "^0.1.0", "chalk": "^5.3.0", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "mocha": "^5.1.1", "monk": "^7.3.4", "nyc": "^15.1.0", @@ -3044,9 +3044,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", - "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", "optional": true, "dependencies": { "sparse-bitfield": "^3.0.3" @@ -7370,9 +7370,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -7501,9 +7501,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -9992,16 +9992,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -13360,10 +13360,27 @@ "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -15579,13 +15596,13 @@ } }, "node_modules/mongoose": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.8.tgz", - "integrity": "sha512-q9zAySH+UtOK5yonWyNcLfq3PxrY6s4gdta4qNGKNOE2yTVoY9FP4hQtvWYnv4rkdk7T8QmQMC7bbhJjDxIunw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.3.tgz", + "integrity": "sha512-eFnbkKgyVrICoHB6tVJ4uLanS7d5AIo/xHkEbQeOv6g2sD7gh/1biRwvFifsmbtkIddQVNr3ROqHik6gkknN3g==", "dependencies": { "bson": "^5.5.0", "kareem": "2.5.1", - "mongodb": "5.9.1", + "mongodb": "5.9.2", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -15608,9 +15625,9 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz", - "integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", "dependencies": { "bson": "^5.5.0", "mongodb-connection-string-url": "^2.6.0", @@ -19884,15 +19901,15 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, diff --git a/package.json b/package.json index 3c6dfa0d6c3..1d823add22c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "5.28.6", + "version": "5.32.0", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.22.10", @@ -28,7 +28,7 @@ "eslint": "^8.55.0", "eslint-config-habitrpg": "^6.2.3", "eslint-plugin-mocha": "^5.0.0", - "express": "^4.21.0", + "express": "^4.21.1", "express-basic-auth": "^1.2.1", "express-validator": "^5.2.0", "firebase-admin": "^12.1.1", @@ -51,7 +51,7 @@ "method-override": "^3.0.0", "moment": "^2.29.4", "moment-recur": "^1.0.7", - "mongoose": "^7.6.3", + "mongoose": "^7.8.3", "morgan": "^1.10.0", "nconf": "^0.12.1", "node-gcm": "^1.0.5", @@ -120,7 +120,7 @@ "chai-as-promised": "^7.1.1", "chai-moment": "^0.1.0", "chalk": "^5.3.0", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "mocha": "^5.1.1", "monk": "^7.3.4", "nyc": "^15.1.0", diff --git a/test/api/unit/libs/bug-report.test.js b/test/api/unit/libs/bug-report.test.js index 58eefca3e65..5cb33d39b49 100644 --- a/test/api/unit/libs/bug-report.test.js +++ b/test/api/unit/libs/bug-report.test.js @@ -44,7 +44,6 @@ describe('bug-report', () => { USER_HOURGLASSES: 0, USER_ID: userId, USER_LEVEL: 1, - USER_OFFSET_MONTHS: 0, USER_PAYMENT_PLATFORM: undefined, USER_SUBSCRIPTION: undefined, USER_TIMEZONE_OFFSET: 0, diff --git a/test/api/unit/libs/cron.test.js b/test/api/unit/libs/cron.test.js index 6757228033e..2863269ff2e 100644 --- a/test/api/unit/libs/cron.test.js +++ b/test/api/unit/libs/cron.test.js @@ -154,6 +154,14 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(1); }); + it('increments plan.cumulativeCount', async () => { + user.purchased.plan.cumulativeCount = 0; + await cron({ + user, tasksByType, daysMissed, analytics, + }); + expect(user.purchased.plan.cumulativeCount).to.equal(1); + }); + it('increments plan.consecutive.count by more than 1 if user skipped months between logins', async () => { user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate(); user.purchased.plan.consecutive.count = 0; @@ -163,12 +171,13 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(2); }); - it('decrements plan.consecutive.offset when offset is greater than 0', async () => { - user.purchased.plan.consecutive.offset = 2; + it('increments plan.cumulativeCount by more than 1 if user skipped months between logins', async () => { + user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate(); + user.purchased.plan.cumulativeCount = 0; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.offset).to.equal(1); + expect(user.purchased.plan.cumulativeCount).to.equal(3); }); it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', async () => { @@ -185,12 +194,12 @@ describe('cron', async () => { }); it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => { - user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('does not reset plan stats if we are before the last day of the cancelled month', async () => { @@ -205,16 +214,14 @@ describe('cron', async () => { user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 }); user.purchased.plan.consecutive.gemCapExtra = 20; user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; await cron({ user, tasksByType, daysMissed, analytics, }); expect(user.purchased.plan.customerId).to.not.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(20); expect(user.purchased.plan.consecutive.count).to.equal(0); - expect(user.purchased.plan.consecutive.offset).to.equal(0); }); describe('for a 1-month recurring subscription', async () => { @@ -236,13 +243,11 @@ describe('cron', async () => { user1.purchased.plan.dateUpdated = moment().toDate(); user1.purchased.plan.planId = 'basic'; user1.purchased.plan.consecutive.count = 0; - user1.purchased.plan.perkMonthCount = 0; - user1.purchased.plan.consecutive.offset = 0; - user1.purchased.plan.consecutive.trinkets = 0; + user1.purchased.plan.consecutive.trinkets = 1; user1.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits after the first month', async () => { + it('increments consecutive benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -253,75 +258,8 @@ describe('cron', async () => { user: user1, tasksByType, daysMissed, analytics, }); expect(user1.purchased.plan.consecutive.count).to.equal(1); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(0); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); - }); - - it('does not increment consecutive benefits after the second month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(2); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(0); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); - }); - - it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => { - user1.purchased.plan.perkMonthCount = 1; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.perkMonthCount).to.equal(0); - expect(user1.purchased.plan.consecutive.count).to.equal(2); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('increments consecutive benefits after the third month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(3); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits after the fourth month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(4); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); + expect(user1.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { @@ -332,33 +270,8 @@ describe('cron', async () => { user: user1, tasksByType, daysMissed, analytics, }); expect(user1.purchased.plan.consecutive.count).to.equal(10); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(3); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15); - }); - - it('initializes plan.perkMonthCount if necessary', async () => { - user.purchased.plan.perkMonthCount = undefined; - clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated) - .utcOffset(0) - .startOf('month') - .add(1, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(user.purchased.plan.perkMonthCount).to.equal(1); - user.purchased.plan.perkMonthCount = undefined; - user.purchased.plan.consecutive.count = 8; - clock.restore(); - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(user.purchased.plan.perkMonthCount).to.equal(2); + expect(user1.purchased.plan.consecutive.trinkets).to.equal(11); + expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(20); }); }); @@ -379,14 +292,12 @@ describe('cron', async () => { user3.purchased.plan.customerId = 'subscribedId'; user3.purchased.plan.dateUpdated = moment().toDate(); user3.purchased.plan.planId = 'basic_3mo'; - user3.purchased.plan.perkMonthCount = 0; user3.purchased.plan.consecutive.count = 0; - user3.purchased.plan.consecutive.offset = 3; user3.purchased.plan.consecutive.trinkets = 1; - user3.purchased.plan.consecutive.gemCapExtra = 5; + user3.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments consecutive benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -394,102 +305,8 @@ describe('cron', async () => { user: user3, tasksByType, daysMissed, analytics, }); expect(user3.purchased.plan.consecutive.count).to.equal(1); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the middle of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(2); - expect(user3.purchased.plan.consecutive.offset).to.equal(1); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(3); - expect(user3.purchased.plan.consecutive.offset).to.equal(0); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(4); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { - user3.purchased.plan.perkMonthCount = 2; - user3.purchased.plan.consecutive.trinkets = 1; - user3.purchased.plan.consecutive.gemCapExtra = 5; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.perkMonthCount).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(5); - expect(user3.purchased.plan.consecutive.offset).to.equal(1); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(6); - expect(user3.purchased.plan.consecutive.offset).to.equal(0); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(7); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(3); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15); + expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { @@ -500,8 +317,7 @@ describe('cron', async () => { user: user3, tasksByType, daysMissed, analytics, }); expect(user3.purchased.plan.consecutive.count).to.equal(10); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(4); + expect(user3.purchased.plan.consecutive.trinkets).to.equal(11); expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20); }); }); @@ -523,14 +339,12 @@ describe('cron', async () => { user6.purchased.plan.customerId = 'subscribedId'; user6.purchased.plan.dateUpdated = moment().toDate(); user6.purchased.plan.planId = 'google_6mo'; - user6.purchased.plan.perkMonthCount = 0; user6.purchased.plan.consecutive.count = 0; - user6.purchased.plan.consecutive.offset = 6; - user6.purchased.plan.consecutive.trinkets = 2; - user6.purchased.plan.consecutive.gemCapExtra = 10; + user6.purchased.plan.consecutive.trinkets = 1; + user6.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -538,74 +352,8 @@ describe('cron', async () => { user: user6, tasksByType, daysMissed, analytics, }); expect(user6.purchased.plan.consecutive.count).to.equal(1); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(6); - expect(user6.purchased.plan.consecutive.offset).to.equal(0); expect(user6.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(7); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { - user6.purchased.plan.perkMonthCount = 2; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.perkMonthCount).to.equal(2); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(13); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(6); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(19); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(8); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); }); @@ -626,11 +374,10 @@ describe('cron', async () => { user12.purchased.plan.dateUpdated = moment().toDate(); user12.purchased.plan.planId = 'basic_12mo'; user12.purchased.plan.consecutive.count = 0; - user12.purchased.plan.consecutive.offset = 12; - user12.purchased.plan.consecutive.trinkets = 4; - user12.purchased.plan.consecutive.gemCapExtra = 20; + user12.purchased.plan.consecutive.trinkets = 1; + user12.purchased.plan.consecutive.gemCapExtra = 26; - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments consecutive benefits the month after the second paid period has started', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -638,61 +385,20 @@ describe('cron', async () => { user: user12, tasksByType, daysMissed, analytics, }); expect(user12.purchased.plan.consecutive.count).to.equal(1); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(12); - expect(user12.purchased.plan.consecutive.offset).to.equal(0); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(13); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(8); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(25); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(12); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user12.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months') + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months') .add(2, 'days') .toDate()); await cron({ user: user12, tasksByType, daysMissed, analytics, }); - expect(user12.purchased.plan.consecutive.count).to.equal(37); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(16); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user12.purchased.plan.consecutive.count).to.equal(10); + expect(user12.purchased.plan.consecutive.trinkets).to.equal(11); + expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); }); @@ -715,11 +421,11 @@ describe('cron', async () => { .toDate(); user3g.purchased.plan.planId = null; user3g.purchased.plan.consecutive.count = 0; - user3g.purchased.plan.consecutive.offset = 3; + user3g.purchased.plan.cumulativeCount = 0; user3g.purchased.plan.consecutive.trinkets = 1; - user3g.purchased.plan.consecutive.gemCapExtra = 5; + user3g.purchased.plan.consecutive.gemCapExtra = 0; - it('does not increment consecutive benefits in the first month of the gift subscription', async () => { + it('increments benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -727,35 +433,9 @@ describe('cron', async () => { user: user3g, tasksByType, daysMissed, analytics, }); expect(user3g.purchased.plan.consecutive.count).to.equal(1); - expect(user3g.purchased.plan.consecutive.offset).to.equal(2); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the second month of the gift subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3g, tasksByType, daysMissed, analytics, - }); - expect(user3g.purchased.plan.consecutive.count).to.equal(2); - expect(user3g.purchased.plan.consecutive.offset).to.equal(1); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the third month of the gift subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3g, tasksByType, daysMissed, analytics, - }); - expect(user3g.purchased.plan.consecutive.count).to.equal(3); - expect(user3g.purchased.plan.consecutive.offset).to.equal(0); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); + expect(user3g.purchased.plan.cumulativeCount).to.equal(1); + expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('does not increment consecutive benefits in the month after the gift subscription has ended', async () => { @@ -767,84 +447,9 @@ describe('cron', async () => { }); // subscription has been erased by now expect(user3g.purchased.plan.consecutive.count).to.equal(0); - expect(user3g.purchased.plan.consecutive.offset).to.equal(0); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased - }); - }); - - describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', async () => { - const user6x = new User({ - auth: { - local: { - username: 'username6x', - lowerCaseUsername: 'username6x', - email: 'email6x@example.com', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }); - // user6x has a 6-month recurring subscription starting 8 months in the past - // before issue #4819 was fixed - user6x.purchased.plan.customerId = 'subscribedId'; - user6x.purchased.plan.dateUpdated = moment().toDate(); - user6x.purchased.plan.planId = 'basic_6mo'; - user6x.purchased.plan.consecutive.count = 8; - user6x.purchased.plan.consecutive.offset = 0; - user6x.purchased.plan.consecutive.trinkets = 3; - user6x.purchased.plan.consecutive.gemCapExtra = 15; - - it('increments consecutive benefits in the first month since the fix for #4819 goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(9); - expect(user6x.purchased.plan.consecutive.offset).to.equal(5); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does not increment consecutive benefits in the second month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(10); - expect(user6x.purchased.plan.consecutive.offset).to.equal(4); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does not increment consecutive benefits in the third month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(11); - expect(user6x.purchased.plan.consecutive.offset).to.equal(3); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits in the seventh month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(15); - expect(user6x.purchased.plan.consecutive.offset).to.equal(5); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2); + expect(user3g.purchased.plan.cumulativeCount).to.equal(1); }); }); }); @@ -888,12 +493,12 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(0); }); - it('does not decrement plan.consecutive.offset when offset is greater than 0', async () => { - user.purchased.plan.consecutive.offset = 1; + it('does not increment plan.cumulativeCount', async () => { + user.purchased.plan.cumulativeCount = 0; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.offset).to.equal(1); + expect(user.purchased.plan.cumulativeCount).to.equal(0); }); it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => { @@ -913,12 +518,12 @@ describe('cron', async () => { }); it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => { - user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('does nothing to plan stats if we are before the last day of the cancelled month', async () => { @@ -928,22 +533,6 @@ describe('cron', async () => { }); expect(user.purchased.plan.customerId).to.not.exist; }); - - xit('does nothing to plan stats when we are after the last day of the cancelled month', async () => { - user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 }); - user.purchased.plan.consecutive.gemCapExtra = 20; - user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; - - await cron({ - user, tasksByType, daysMissed, analytics, - }); - - expect(user.purchased.plan.customerId).to.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.exist; - expect(user.purchased.plan.consecutive.count).to.exist; - expect(user.purchased.plan.consecutive.offset).to.exist; - }); }); describe('todos', async () => { diff --git a/test/api/unit/libs/payments/group-plans/group-payments-create.test.js b/test/api/unit/libs/payments/group-plans/group-payments-create.test.js index 383d7dfe6f3..17ff154a0a0 100644 --- a/test/api/unit/libs/payments/group-plans/group-payments-create.test.js +++ b/test/api/unit/libs/payments/group-plans/group-payments-create.test.js @@ -715,7 +715,7 @@ describe('Purchasing a group plan for group', () => { const mysteryItem = { title: 'item' }; const mysteryItems = [mysteryItem]; const consecutive = { - trinkets: 3, + trinkets: 4, gemCapExtra: 20, offset: 1, count: 13, diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 5b2d2f98990..00732af3cea 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -12,6 +12,7 @@ import { } from '../../../../helpers/api-unit.helper'; import * as worldState from '../../../../../website/server/libs/worldState'; import { TransactionModel } from '../../../../../website/server/models/transaction'; +import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events'; describe('payments/index', () => { let user; @@ -65,7 +66,6 @@ describe('payments/index', () => { mysteryItems: [], consecutive: { trinkets: 0, - offset: 0, gemCapExtra: 0, }, }; @@ -108,14 +108,8 @@ describe('payments/index', () => { }); it('add a transaction entry to the recipient', async () => { - recipient.purchased.plan = plan; - - expect(recipient.purchased.plan.extraMonths).to.eql(0); - await api.createSubscription(data); - expect(recipient.purchased.plan.extraMonths).to.eql(3); - const transactions = await TransactionModel .find({ userId: recipient._id }) .sort({ createdAt: -1 }) @@ -177,6 +171,45 @@ describe('payments/index', () => { expect(recipient.purchased.plan.dateUpdated).to.exist; }); + it('does not reset gemCapExtra if they already had one', async () => { + recipient.purchased.plan.consecutive.gemCapExtra = 10; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('sets gemCapExtra to 0 if they receive a 3 month sub', async () => { + data.gift.subscription.key = 'basic_3mo'; + data.gift.subscription.months = 3; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + }); + + it('sets gemCapExtra to max if they receive a 12 month sub', async () => { + recipient.purchased.plan.consecutive.gemCapExtra = 10; + + data.gift.subscription.key = 'basic_12mo'; + data.gift.subscription.months = 12; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(26); + }); + + it('gives user 1 hourglass if they have no active subscription', async () => { + await api.createSubscription(data); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + }); + + it('does not give any hourglasses if they have an active subscription', async () => { + recipient.purchased.plan = plan; + await api.createSubscription(data); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(plan.consecutive.trinkets); + }); + it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => { recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate(); recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate(); @@ -235,116 +268,6 @@ describe('payments/index', () => { expect(recipient.purchased.plan.customerId).to.eql('customer-id'); }); - it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 1; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('sets plan.perkMonthCount to 1 if field is not initialized', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = -1; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('adds to plan.perkMonthCount if user is already subscribed', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 1; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - }); - - it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => { - recipient.purchased.plan.perkMonthCount = 0; - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => { - expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount goes over 3', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - data.sub.key = 'basic_earned'; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - it('sets plan.customerId to "Gift" if it does not already exist', async () => { expect(recipient.purchased.plan.customerId).to.not.exist; @@ -421,8 +344,8 @@ describe('payments/index', () => { context('Active Promotion', () => { beforeEach(() => { sinon.stub(worldState, 'getCurrentEventList').returns([{ - ...common.content.events.winter2021Promo, - event: 'winter2021', + ...REPEATING_EVENTS.giftOneGetOne, + event: 'g1g1', }]); }); @@ -438,22 +361,30 @@ describe('payments/index', () => { expect(user.purchased.plan.dateTerminated).to.exist; expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.dateCreated).to.exist; + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(recipient.purchased.plan.customerId).to.eql('Gift'); expect(recipient.purchased.plan.dateTerminated).to.exist; expect(recipient.purchased.plan.dateUpdated).to.exist; expect(recipient.purchased.plan.dateCreated).to.exist; + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); }); it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => { user.purchased.plan = plan; expect(user.purchased.plan.extraMonths).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); await api.createSubscription(data); expect(user.purchased.plan.extraMonths).to.eql(3); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(recipient.purchased.plan.customerId).to.eql('Gift'); @@ -466,10 +397,12 @@ describe('payments/index', () => { recipient.purchased.plan = plan; expect(recipient.purchased.plan.extraMonths).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); await api.createSubscription(data); expect(recipient.purchased.plan.extraMonths).to.eql(3); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(user.purchased.plan.customerId).to.eql('Gift'); @@ -484,11 +417,15 @@ describe('payments/index', () => { expect(user.purchased.plan.extraMonths).to.eql(0); expect(recipient.purchased.plan.extraMonths).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); await api.createSubscription(data); expect(user.purchased.plan.extraMonths).to.eql(3); expect(recipient.purchased.plan.extraMonths).to.eql(3); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); }); it('sends a private message about the promotion', async () => { @@ -511,7 +448,6 @@ describe('payments/index', () => { expect(user.purchased.plan.customerId).to.eql('customer-id'); expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.gemsBought).to.eql(0); - expect(user.purchased.plan.perkMonthCount).to.eql(0); expect(user.purchased.plan.paymentMethod).to.eql('Payment Method'); expect(user.purchased.plan.extraMonths).to.eql(0); expect(user.purchased.plan.dateTerminated).to.eql(null); @@ -549,33 +485,6 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate); }); - it('keeps plan.perkMonthCount when changing subscription type', async () => { - await api.createSubscription(data); - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(2); - }); - - it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => { - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(0); - }); - - it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => { - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(0); - }); - - it('updates plan.consecutive.offset when changing subscription type', async () => { - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(3); - data.sub.key = 'basic_6mo'; - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(6); - }); - it('awards the Royal Purple Jackalope pet', async () => { await api.createSubscription(data); @@ -694,6 +603,7 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCreated).to.eql(created); expect(user.purchased.plan.dateUpdated).to.not.eql(updated); expect(user.purchased.plan.customerId).to.eql('customer-id'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); }); @@ -741,77 +651,51 @@ describe('payments/index', () => { }); context('Block subscription perks', () => { - it('adds block months to plan.consecutive.offset', async () => { + it('adds 26 to plan.consecutive.gemCapExtra for 12 month block', async () => { + data.sub.key = 'basic_12mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(3); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('does not add to plans.consecutive.offset if 1 month subscription', async () => { - data.sub.key = 'basic_earned'; - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.offset).to.eql(0); - }); + it('does not raise plan.consecutive.gemCapExtra higher than 26', async () => { + data.sub.key = 'basic_12mo'; - it('resets plans.consecutive.offset if 1 month subscription', async () => { - user.purchased.plan.consecutive.offset = 1; - await user.save(); - data.sub.key = 'basic_earned'; + await api.createSubscription(data); await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => { + it('adds a plan.consecutive.trinkets for 3 month block', async () => { await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => { + it('adds 1 plan.consecutive.trinkets for 6 month block', async () => { data.sub.key = 'basic_6mo'; - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => { - data.sub.key = 'basic_12mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('does not raise plan.consecutive.gemCapExtra higher than 25', async () => { + it('adds 1 plan.consecutive.trinkets for 12 month block if they had promo', async () => { + user.purchased.plan.hourglassPromoReceived = new Date(); data.sub.key = 'basic_12mo'; - await api.createSubscription(data); - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); - }); - - it('adds a plan.consecutive.trinkets for 3 month block', async () => { await api.createSubscription(data); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('adds 2 plan.consecutive.trinkets for 6 month block', async () => { - data.sub.key = 'basic_6mo'; - - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('adds 4 plan.consecutive.trinkets for 12 month block', async () => { + it('adds 12 plan.consecutive.trinkets for 12 month block', async () => { data.sub.key = 'basic_12mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); context('Upgrades subscription', () => { @@ -819,70 +703,38 @@ describe('payments/index', () => { beforeEach(async () => { data.updatedFrom = { logic: 'payDifference' }; }); - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -894,7 +746,7 @@ describe('payments/index', () => { data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); @@ -902,70 +754,39 @@ describe('payments/index', () => { beforeEach(async () => { data.updatedFrom = { logic: 'payFull' }; }); - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); - }); - - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -977,7 +798,7 @@ describe('payments/index', () => { data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); @@ -988,30 +809,13 @@ describe('payments/index', () => { data.updatedFrom = { logic: 'refundAndRepay' }; }); context('Upgrades within first half of subscription', () => { - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2022-01-10')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; @@ -1019,28 +823,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-02-05')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - }); - - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2022-01-08')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1054,17 +840,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-31')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1072,35 +858,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2024-01-08')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + it('2 plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1108,10 +876,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-08-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1125,11 +893,11 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-07-31')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); context('Upgrades within second half of subscription', () => { - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + it('Adds 0 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; @@ -1144,16 +912,16 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-20')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); }); - it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; @@ -1161,17 +929,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-02-24')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_6mo'; data.updatedFrom.key = 'basic_earned'; @@ -1179,17 +947,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1197,10 +965,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1214,17 +982,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-03-03')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { + it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_6mo'; data.updatedFrom.key = 'basic_earned'; @@ -1232,17 +1000,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1250,10 +1018,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2023-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1267,7 +1035,7 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2023-09-03')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); afterEach(async () => { @@ -1277,22 +1045,6 @@ describe('payments/index', () => { }); context('Downgrades subscription', () => { - it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => { expect(user.purchased.plan.planId).to.not.exist; @@ -1300,28 +1052,12 @@ describe('payments/index', () => { await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); data.sub.key = 'basic_3mo'; data.updatedFrom = { key: 'basic_12mo' }; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - }); - - it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => { @@ -1331,12 +1067,12 @@ describe('payments/index', () => { await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); data.sub.key = 'basic_3mo'; data.updatedFrom = { key: 'basic_12mo' }; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); }); @@ -1453,6 +1189,32 @@ describe('payments/index', () => { expect(user.purchased.plan.extraMonths).to.eql(0); }); + it('does not reset gemCapExtra', async () => { + user.purchased.plan.consecutive.gemCapExtra = 12; + + await api.cancelSubscription(data); + + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(12); + }); + + it('initializes gemCapExtra', async () => { + await api.cancelSubscription(data); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + }); + + it('initializes hourglasses', async () => { + await api.cancelSubscription(data); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + }); + + it('does not reset owned hourglasses', async () => { + user.purchased.plan.consecutive.trinkets = 12; + + await api.cancelSubscription(data); + + expect(user.purchased.plan.consecutive.trinkets).to.eql(12); + }); + it('sends an email', async () => { await api.cancelSubscription(data); diff --git a/test/api/unit/libs/payments/stripe/checkout.test.js b/test/api/unit/libs/payments/stripe/checkout.test.js index ab724924016..42b05f6efb2 100644 --- a/test/api/unit/libs/payments/stripe/checkout.test.js +++ b/test/api/unit/libs/payments/stripe/checkout.test.js @@ -51,6 +51,7 @@ describe('Stripe - Checkout', () => { gift: undefined, sub: undefined, gemsBlock: gemsBlockKey, + server_url: BASE_URL, }; expect(gems.validateGiftMessage).to.not.be.called; @@ -101,6 +102,7 @@ describe('Stripe - Checkout', () => { gift: JSON.stringify(gift), sub: undefined, gemsBlock: undefined, + server_url: BASE_URL, }; expect(gems.validateGiftMessage).to.be.calledOnce; @@ -155,6 +157,7 @@ describe('Stripe - Checkout', () => { gift: JSON.stringify(gift), sub: undefined, gemsBlock: undefined, + server_url: BASE_URL, }; expect(oneTimePayments.getOneTimePaymentInfo).to.be.calledOnce; @@ -192,6 +195,7 @@ describe('Stripe - Checkout', () => { userId: user._id, gift: undefined, sub: JSON.stringify(sub), + server_url: BASE_URL, }; expect(subscriptions.checkSubData).to.be.calledOnce; @@ -258,6 +262,7 @@ describe('Stripe - Checkout', () => { userId: user._id, gift: undefined, sub: JSON.stringify(sub), + server_url: BASE_URL, groupId, }; @@ -328,8 +333,9 @@ describe('Stripe - Checkout', () => { user.purchased.plan.customerId = customerId; const metadata = { - userId: user._id, type: 'edit-card-user', + userId: user._id, + server_url: BASE_URL, }; const res = await createEditCardCheckoutSession({ user }, stripe); @@ -418,6 +424,7 @@ describe('Stripe - Checkout', () => { const metadata = { userId: user._id, type: 'edit-card-group', + server_url: BASE_URL, groupId, }; @@ -455,6 +462,7 @@ describe('Stripe - Checkout', () => { userId: anotherUser._id, type: 'edit-card-group', groupId, + server_url: BASE_URL, }; const res = await createEditCardCheckoutSession({ user: anotherUser, groupId }, stripe); diff --git a/test/api/unit/libs/payments/stripe/oneTimePayments.test.js b/test/api/unit/libs/payments/stripe/oneTimePayments.test.js index c52cd162b4a..7ccb85b8eee 100644 --- a/test/api/unit/libs/payments/stripe/oneTimePayments.test.js +++ b/test/api/unit/libs/payments/stripe/oneTimePayments.test.js @@ -308,6 +308,7 @@ describe('Stripe - One Time Payments', () => { customerId, paymentMethod: 'Gift', gift, + autoRenews: false, gemsBlock: undefined, }); }); diff --git a/test/api/unit/libs/payments/stripe/subscriptions.test.js b/test/api/unit/libs/payments/stripe/subscriptions.test.js index c135f94b276..3955037c020 100644 --- a/test/api/unit/libs/payments/stripe/subscriptions.test.js +++ b/test/api/unit/libs/payments/stripe/subscriptions.test.js @@ -173,6 +173,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId: null, + autoRenews: true, }); }); @@ -197,6 +198,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId, + autoRenews: true, }); }); @@ -231,6 +233,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId, + autoRenews: true, }); }); }); diff --git a/test/api/unit/libs/payments/stripe/webhooks.test.js b/test/api/unit/libs/payments/stripe/webhooks.test.js index 816a0a9faa7..64d6490e846 100644 --- a/test/api/unit/libs/payments/stripe/webhooks.test.js +++ b/test/api/unit/libs/payments/stripe/webhooks.test.js @@ -16,6 +16,7 @@ import * as subscriptions from '../../../../../../website/server/libs/payments/s const { i18n } = common; describe('Stripe - Webhooks', () => { + const BASE_URL = nconf.get('BASE_URL'); const stripe = stripeModule('test'); const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET'); const headers = {}; @@ -284,7 +285,9 @@ describe('Stripe - Webhooks', () => { const session = {}; beforeEach(() => { - session.metadata = {}; + session.metadata = { + server_url: BASE_URL, + }; event = { type: eventType, data: { object: session } }; constructEventStub = sandbox.stub(stripe.webhooks, 'constructEvent'); constructEventStub.returns(event); diff --git a/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js index d4df2c93b3f..cd0576d13a6 100644 --- a/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js +++ b/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js @@ -31,7 +31,7 @@ describe('POST /user/buy-mystery-set/:key', () => { expect(res.data).to.eql({ items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared - purchasedPlanConsecutive: user.purchased.plan.consecutive, + purchasedPlanConsecutive: JSON.parse(JSON.stringify(user.purchased.plan.consecutive)), }); expect(res.message).to.equal(t('hourglassPurchaseSet')); }); diff --git a/test/common/libs/cron.test.js b/test/common/libs/cron.test.js index e8be92fceb5..c2e8f7b32bb 100644 --- a/test/common/libs/cron.test.js +++ b/test/common/libs/cron.test.js @@ -183,8 +183,6 @@ describe('cron utility functions', () => { }); describe('getPlanContext', () => { - const now = new Date(2022, 5, 1); - function baseUserData (count, offset, planId) { return { purchased: { @@ -192,7 +190,7 @@ describe('cron utility functions', () => { consecutive: { count, offset, - gemCapExtra: 25, + gemCapExtra: 26, trinkets: 19, }, quantity: 1, @@ -213,52 +211,19 @@ describe('cron utility functions', () => { }; } - it('monthly plan, next date in 3 months', () => { + it('elapsedMonths is 0 if its the same month', () => { const user = baseUserData(60, 0, 'group_plan_auto'); - user.purchased.plan.perkMonthCount = 0; - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-08-10T02:00:00.144Z'); - }); - it('monthly plan, next date in 1 month', () => { - const user = baseUserData(62, 0, 'group_plan_auto'); - user.purchased.plan.perkMonthCount = 2; - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-06-10T02:00:00.144Z'); + const planContext = getPlanContext(user, new Date(2022, 4, 20)); + expect(planContext.elapsedMonths).to.equal(0); }); - it('multi-month plan, no offset', () => { - const user = baseUserData(60, 0, 'basic_3mo'); - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-06-10T02:00:00.144Z'); - }); - - it('multi-month plan with offset', () => { - const user = baseUserData(60, 1, 'basic_3mo'); - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-07-10T02:00:00.144Z'); - }); - - it('multi-month plan with perk count', () => { - const user = baseUserData(60, 1, 'basic_3mo'); - user.purchased.plan.perkMonthCount = 2; + it('elapsedMonths is 1 after one month', () => { + const user = baseUserData(60, 0, 'group_plan_auto'); - const planContext = getPlanContext(user, now); + const planContext = getPlanContext(user, new Date(2022, 5, 11)); - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-07-10T02:00:00.144Z'); + expect(planContext.elapsedMonths).to.equal(1); }); }); }); diff --git a/test/content/quests.test.js b/test/content/quests.test.js new file mode 100644 index 00000000000..52cf70dbcf3 --- /dev/null +++ b/test/content/quests.test.js @@ -0,0 +1,42 @@ +import { + each, +} from 'lodash'; +import { + expectValidTranslationString, +} from '../helpers/content.helper'; + +import { quests } from '../../website/common/script/content/quests'; + +describe('quests', () => { + let clock; + + afterEach(() => { + if (clock) { + clock.restore(); + } + }); + + it('contains basic information about each quest', () => { + each(quests, (quest, key) => { + expectValidTranslationString(quest.text); + expectValidTranslationString(quest.notes); + expectValidTranslationString(quest.completion); + expect(quest.key, key).to.equal(key); + expect(quest.category, key).to.be.a('string'); + if (quest.boss) { + expectValidTranslationString(quest.boss.name); + expect(quest.boss.hp, key).to.be.a('number'); + expect(quest.boss.str, key).to.be.a('number'); + } + expect(quest.drop).to.be.an('object'); + expect(quest.drop.gp, key).to.be.a('number'); + expect(quest.drop.exp, key).to.be.a('number'); + if (quest.drop.items) { + quest.drop.items.forEach(drop => { + expectValidTranslationString(drop.text); + expect(drop.type, key).to.exist; + }); + } + }); + }); +}); diff --git a/test/content/releaseDates.test.js b/test/content/releaseDates.test.js index 1c5fad92b3d..bdf53e6ddbb 100644 --- a/test/content/releaseDates.test.js +++ b/test/content/releaseDates.test.js @@ -19,8 +19,8 @@ describe('releaseDates', () => { }); describe('armoire', () => { it('should only contain valid armoire names', () => { - const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-20`)); - clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-20`)); + const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-22`)); + clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-22`)); Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => { expect(find(armoire.all, { set: key }), `${key} is not a valid armoire set`).to.exist; }); @@ -40,8 +40,8 @@ describe('releaseDates', () => { describe('eggs', () => { it('should only contain valid egg names', () => { - const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`)); - clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`)); + const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`)); + clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`)); Object.keys(EGGS_RELEASE_DATES).forEach(key => { expect(eggs.all[key], `${key} is not a valid egg name`).to.exist; }); @@ -61,8 +61,8 @@ describe('releaseDates', () => { describe('hatchingPotions', () => { it('should only contain valid potion names', () => { - const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`)); - clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`)); + const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`)); + clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`)); Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => { expect(hatchingPotions.all[key], `${key} is not a valid potion name`).to.exist; }); diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index 6e8c172a984..de449381268 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -1,4 +1,5 @@ // eslint-disable-next-line max-len +import maxBy from 'lodash/maxBy'; import moment from 'moment'; import nconf from 'nconf'; import { @@ -10,6 +11,7 @@ import QUEST_BUNDLES from '../../website/common/script/content/bundles'; import potions from '../../website/common/script/content/hatching-potions'; import SPELLS from '../../website/common/script/content/spells'; import QUEST_SEASONAL from '../../website/common/script/content/quests/seasonal'; +import { HATCHING_POTIONS_RELEASE_DATES } from '../../website/common/script/content/constants/releaseDates'; function validateMatcher (matcher, checkedDate) { expect(matcher.end).to.be.a('date'); @@ -222,6 +224,8 @@ describe('Content Schedule', () => { }); it('premium hatching potions', () => { + const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month}-${value.day}`)); + clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month}-${lastReleaseDate.day + 1}`)); const potionKeys = Object.keys(potions.premium); Object.keys(MONTHLY_SCHEDULE).forEach(key => { const monthlyPotions = MONTHLY_SCHEDULE[key][21].find(item => item.type === 'premiumHatchingPotions'); diff --git a/website/client/package-lock.json b/website/client/package-lock.json index 59e71df90e9..e8f09c55daf 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -38,7 +38,6 @@ "sass": "^1.63.4", "sass-loader": "^14.1.1", "sinon": "^17.0.1", - "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", "timers-browserify": "^2.0.12", "uuid": "^9.0.1", @@ -3935,9 +3934,9 @@ "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -3947,7 +3946,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4141,6 +4140,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4617,9 +4643,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -5423,6 +5449,19 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -5465,9 +5504,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -5589,12 +5628,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -5612,6 +5648,17 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -6696,36 +6743,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6734,6 +6781,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/array-flatten": { @@ -6878,12 +6929,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -7126,15 +7177,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7254,11 +7310,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7354,9 +7410,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -7384,9 +7440,9 @@ "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==" }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -8920,6 +8976,14 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -8959,9 +9023,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-source-map": { "version": "1.1.0", @@ -9863,9 +9930,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10311,9 +10381,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -11130,11 +11200,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11737,9 +11807,9 @@ "dev": true }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11772,6 +11842,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11856,14 +11934,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -11952,13 +12030,68 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12080,17 +12213,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/smartbanner.js": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.22.0.tgz", - "integrity": "sha512-JhERLgwEPuzVdwAHds1J6txWBVq9BwmlAn+5VicrAfIOMO3ehNA7VHu8IIJNnW1LsElSCaLWxjdLjlEwLDqAvA==", - "engines": { - "node": ">=10.24.1 <22.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ain" - } - }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", diff --git a/website/client/package.json b/website/client/package.json index e19536a787e..e9b1fe2689d 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -40,7 +40,6 @@ "sass": "^1.63.4", "sass-loader": "^14.1.1", "sinon": "^17.0.1", - "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", "timers-browserify": "^2.0.12", "uuid": "^9.0.1", diff --git a/website/client/public/index.html b/website/client/public/index.html index 2574b000d1e..2b77fedd6a1 100644 --- a/website/client/public/index.html +++ b/website/client/public/index.html @@ -7,18 +7,6 @@