diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js similarity index 86% rename from test/api/v3/integration/groups/POST-groups_groupId_leave.js rename to test/api/v3/integration/groups/POST-groups_groupId_leave.test.js index c4764e3428..fea70d3176 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_leave.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js @@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid'; import { each, } from 'lodash'; +import moment from 'moment'; import { generateChallenge, checkExistence, @@ -12,6 +13,7 @@ import { } from '../../../../helpers/api-integration/v3'; import { model as User } from '../../../../../website/server/models/user'; import payments from '../../../../../website/server/libs/payments/payments'; +import { calculateSubscriptionTerminationDate } from '../../../../../website/server/libs/payments/util'; describe('POST /groups/:groupId/leave', () => { const typesOfGroups = { @@ -338,4 +340,48 @@ describe('POST /groups/:groupId/leave', () => { }); }); }); + + each(typesOfGroups, (groupDetails, groupType) => { + context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => { + const extraMonths = 12; + let groupWithPlan; + let member; + + beforeEach(async () => { + const { group, members } = await createAndPopulateGroup({ + groupDetails, + members: 1, + upgradeToGroupPlan: true, + }); + [member] = members; + groupWithPlan = group; + await member.update({ + 'purchased.plan.extraMonths': extraMonths, + }); + }); + it('calculates dateTerminated and sets extraMonths to zero after user leaves the group', async () => { + const userBeforeLeave = await User.findById(member._id).exec(); + + await member.post(`/groups/${groupWithPlan._id}/leave`); + const userAfterLeave = await User.findById(member._id).exec(); + + const dateTerminatedBefore = userBeforeLeave.purchased.plan.dateTerminated; + const extraMonthsBefore = userBeforeLeave.purchased.plan.extraMonths; + const dateTerminatedAfter = userAfterLeave.purchased.plan.dateTerminated; + const extraMonthsAfter = userAfterLeave.purchased.plan.extraMonths; + + const expectedTerminationDate = calculateSubscriptionTerminationDate(null, { + customerId: payments.constants.GROUP_PLAN_CUSTOMER_ID, + extraMonths, + }, payments.constants); + + expect(extraMonthsBefore).to.gte(12); + expect(extraMonthsAfter).to.equal(0); + expect(dateTerminatedBefore).to.be.null; + expect(dateTerminatedAfter).to.exist; + + expect(moment(dateTerminatedAfter).diff(expectedTerminationDate, 'days')).to.equal(0); + }); + }); + }); }); diff --git a/test/helpers/api-integration/v3/object-generators.js b/test/helpers/api-integration/v3/object-generators.js index e713c1084c..b48dd862a9 100644 --- a/test/helpers/api-integration/v3/object-generators.js +++ b/test/helpers/api-integration/v3/object-generators.js @@ -5,6 +5,8 @@ import { v4 as generateUUID } from 'uuid'; import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; import { requester } from '../requester'; import * as Tasks from '../../../../website/server/models/task'; +import payments from '../../../../website/server/libs/payments/payments'; +import { model as User } from '../../../../website/server/models/user'; // Creates a new user and returns it // If you need the user to have specific requirements, @@ -83,14 +85,35 @@ export async function generateGroup (leader, details = {}, update = {}) { return apiGroup; } +async function _upgradeToGroupPlan (groupLeader, group) { + const groupLeaderModel = await User.findById(groupLeader._id).exec(); + + // Create subscription + const paymentData = { + user: groupLeaderModel, + groupId: group._id, + sub: { + key: 'basic_3mo', + }, + customerId: 'customer-id', + paymentMethod: 'Payment Method', + headers: { + 'x-client': 'habitica-web', + 'user-agent': '', + }, + }; + await payments.createSubscription(paymentData); +} + // This is generate group + the ability to create // real users to populate it. The settings object // takes in: // members: Number - the number of group members to create. // Defaults to 0. Does not include group leader. -// inivtes: Number - the number of users to create and invite to the group. Defaults to 0. +// invites: Number - the number of users to create and invite to the group. Defaults to 0. // groupDetails: Object - how to initialize the group // leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user +// addGroupPlan: boolean - will add group plan with basic subscription. Defaults to false // can create the group // // Returns an object with @@ -101,6 +124,7 @@ export async function generateGroup (leader, details = {}, update = {}) { export async function createAndPopulateGroup (settings = {}) { const numberOfMembers = settings.members || 0; const numberOfInvites = settings.invites || 0; + const upgradeToGroupPlan = settings.upgradeToGroupPlan || false; const { groupDetails } = settings; const leaderDetails = settings.leaderDetails || { balance: 10 }; @@ -130,6 +154,10 @@ export async function createAndPopulateGroup (settings = {}) { await Promise.all(invitees.map(invitee => invitee.sync())); + if (upgradeToGroupPlan) { + await _upgradeToGroupPlan(groupLeader, group); + } + return { groupLeader, group, diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index b90b226659..a35a6b8616 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -861,11 +861,6 @@ api.leaveGroup = { if (guildIndex >= 0) user.guilds.splice(guildIndex, 1); } - const isMemberOfGroupPlan = await user.isMemberOfGroupPlan(); - if (!isMemberOfGroupPlan) { - await payments.cancelGroupSubscriptionForUser(user, group); - } - if (group.hasNotCancelled()) await group.updateGroupPlan(true); res.respond(200, {}); },