SubscriptionUpdaterTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const modulePath =
  4. '../../../../app/src/Features/Subscription/SubscriptionUpdater'
  5. const { assert, expect } = require('chai')
  6. const { ObjectId } = require('mongodb-legacy')
  7. describe('SubscriptionUpdater', function () {
  8. beforeEach(function () {
  9. this.recurlyPlan = { planCode: 'recurly-plan' }
  10. this.recurlySubscription = {
  11. uuid: '1238uoijdasjhd',
  12. plan: {
  13. plan_code: this.recurlyPlan.planCode,
  14. },
  15. }
  16. this.adminUser = { _id: (this.adminuser_id = '5208dd34438843e2db000007') }
  17. this.otherUserId = '5208dd34438842e2db000005'
  18. this.allUserIds = ['13213', 'dsadas', 'djsaiud89']
  19. this.subscription = {
  20. _id: '111111111111111111111111',
  21. admin_id: this.adminUser._id,
  22. manager_ids: [this.adminUser._id],
  23. member_ids: [],
  24. save: sinon.stub().resolves(),
  25. planCode: 'student_or_something',
  26. recurlySubscription_id: 'abc123def456fab789',
  27. }
  28. this.user_id = this.adminuser_id
  29. this.groupSubscription = {
  30. _id: '222222222222222222222222',
  31. admin_id: this.adminUser._id,
  32. manager_ids: [this.adminUser._id],
  33. member_ids: this.allUserIds,
  34. save: sinon.stub().resolves(),
  35. groupPlan: true,
  36. planCode: 'group_subscription',
  37. recurlySubscription_id: '456fab789abc123def',
  38. }
  39. this.betterGroupSubscription = {
  40. _id: '999999999999999999999999',
  41. admin_id: this.adminUser._id,
  42. manager_ids: [this.adminUser._id],
  43. member_ids: [this.otherUserId],
  44. save: sinon.stub().resolves(),
  45. groupPlan: true,
  46. planCode: 'better_group_subscription',
  47. recurlySubscription_id: '123def456fab789abc',
  48. }
  49. const subscription = this.subscription
  50. this.SubscriptionModel = class {
  51. constructor(opts) {
  52. // Always return our mock subscription when creating a new one
  53. subscription.admin_id = opts.admin_id
  54. subscription.manager_ids = [opts.admin_id]
  55. return subscription
  56. }
  57. save() {
  58. return Promise.resolve(subscription)
  59. }
  60. }
  61. this.SubscriptionModel.deleteOne = sinon
  62. .stub()
  63. .returns({ exec: sinon.stub().resolves() })
  64. this.SubscriptionModel.updateOne = sinon
  65. .stub()
  66. .returns({ exec: sinon.stub().resolves() })
  67. this.SubscriptionModel.findOne = sinon.stub().resolves()
  68. this.SubscriptionModel.updateMany = sinon
  69. .stub()
  70. .returns({ exec: sinon.stub().resolves() })
  71. this.SubscriptionModel.findOneAndUpdate = sinon.stub().returns({
  72. exec: sinon.stub().resolves(this.subscription),
  73. })
  74. this.SSOConfigModel = class {}
  75. this.SSOConfigModel.findOne = sinon.stub().returns({
  76. lean: sinon.stub().returns({
  77. exec: sinon.stub().resolves({ enabled: true }),
  78. }),
  79. })
  80. this.SubscriptionLocator = {
  81. promises: {
  82. getUsersSubscription: sinon.stub(),
  83. getGroupSubscriptionMemberOf: sinon.stub(),
  84. getMemberSubscriptions: sinon.stub().resolves([]),
  85. getSubscription: sinon.stub(),
  86. },
  87. }
  88. this.SubscriptionLocator.promises.getSubscription
  89. .withArgs(this.subscription._id)
  90. .resolves(this.subscription)
  91. this.Settings = {
  92. defaultPlanCode: 'personal',
  93. defaultFeatures: { default: 'features' },
  94. plans: [
  95. this.recurlyPlan,
  96. { planCode: this.subscription.planCode, features: {} },
  97. {
  98. planCode: this.groupSubscription.planCode,
  99. features: {
  100. collaborators: 10,
  101. compileTimeout: 60,
  102. dropbox: true,
  103. },
  104. },
  105. {
  106. planCode: this.betterGroupSubscription.planCode,
  107. features: {
  108. collaborators: -1,
  109. compileTimeout: 240,
  110. dropbox: true,
  111. },
  112. },
  113. ],
  114. }
  115. this.UserFeaturesUpdater = {
  116. promises: {
  117. updateFeatures: sinon.stub().resolves(),
  118. },
  119. }
  120. this.ReferalFeatures = {
  121. promises: {
  122. getBonusFeatures: sinon.stub().resolves(),
  123. },
  124. }
  125. this.FeaturesUpdater = {
  126. promises: {
  127. scheduleRefreshFeatures: sinon.stub().resolves(),
  128. refreshFeatures: sinon.stub().resolves({}),
  129. },
  130. }
  131. this.DeletedSubscription = {
  132. findOneAndUpdate: sinon.stub().returns({ exec: sinon.stub().resolves() }),
  133. }
  134. this.AnalyticsManager = {
  135. recordEventForUserInBackground: sinon.stub().resolves(),
  136. setUserPropertyForUserInBackground: sinon.stub(),
  137. }
  138. this.Features = {
  139. hasFeature: sinon.stub().returns(false),
  140. }
  141. this.UserAuditLogHandler = {
  142. promises: {
  143. addEntry: sinon.stub().resolves(),
  144. },
  145. }
  146. this.SubscriptionUpdater = SandboxedModule.require(modulePath, {
  147. requires: {
  148. '../../models/Subscription': {
  149. Subscription: this.SubscriptionModel,
  150. },
  151. '../../models/SSOConfig': { SSOConfig: this.SSOConfigModel },
  152. './UserFeaturesUpdater': this.UserFeaturesUpdater,
  153. './SubscriptionLocator': this.SubscriptionLocator,
  154. '@overleaf/settings': this.Settings,
  155. '../../infrastructure/mongodb': { db: {}, ObjectId },
  156. './FeaturesUpdater': this.FeaturesUpdater,
  157. '../../models/DeletedSubscription': {
  158. DeletedSubscription: this.DeletedSubscription,
  159. },
  160. '../Analytics/AnalyticsManager': this.AnalyticsManager,
  161. '../../infrastructure/Features': this.Features,
  162. '../User/UserAuditLogHandler': this.UserAuditLogHandler,
  163. },
  164. })
  165. })
  166. describe('updateAdmin', function () {
  167. it('should update the subscription admin', async function () {
  168. this.subscription.groupPlan = true
  169. await this.SubscriptionUpdater.promises.updateAdmin(
  170. this.subscription,
  171. this.otherUserId
  172. )
  173. const query = {
  174. _id: new ObjectId(this.subscription._id),
  175. customAccount: true,
  176. }
  177. const update = {
  178. $set: { admin_id: new ObjectId(this.otherUserId) },
  179. $addToSet: { manager_ids: new ObjectId(this.otherUserId) },
  180. }
  181. this.SubscriptionModel.updateOne.should.have.been.calledOnce
  182. this.SubscriptionModel.updateOne.should.have.been.calledWith(
  183. query,
  184. update
  185. )
  186. })
  187. it('should remove the manager for non-group subscriptions', async function () {
  188. await this.SubscriptionUpdater.promises.updateAdmin(
  189. this.subscription,
  190. this.otherUserId
  191. )
  192. const query = {
  193. _id: new ObjectId(this.subscription._id),
  194. customAccount: true,
  195. }
  196. const update = {
  197. $set: {
  198. admin_id: new ObjectId(this.otherUserId),
  199. manager_ids: [new ObjectId(this.otherUserId)],
  200. },
  201. }
  202. this.SubscriptionModel.updateOne.should.have.been.calledOnce
  203. this.SubscriptionModel.updateOne.should.have.been.calledWith(
  204. query,
  205. update
  206. )
  207. })
  208. })
  209. describe('syncSubscription', function () {
  210. beforeEach(function () {
  211. this.SubscriptionLocator.promises.getUsersSubscription.resolves(
  212. this.subscription
  213. )
  214. })
  215. it('should update the subscription if the user already is admin of one', async function () {
  216. await this.SubscriptionUpdater.promises.syncSubscription(
  217. this.recurlySubscription,
  218. this.adminUser._id
  219. )
  220. this.SubscriptionLocator.promises.getUsersSubscription
  221. .calledWith(this.adminUser._id)
  222. .should.equal(true)
  223. })
  224. it('should not call updateFeatures with group subscription if recurly subscription is not expired', async function () {
  225. await this.SubscriptionUpdater.promises.syncSubscription(
  226. this.recurlySubscription,
  227. this.adminUser._id
  228. )
  229. this.SubscriptionLocator.promises.getUsersSubscription
  230. .calledWith(this.adminUser._id)
  231. .should.equal(true)
  232. this.UserFeaturesUpdater.promises.updateFeatures.called.should.equal(
  233. false
  234. )
  235. })
  236. })
  237. describe('updateSubscriptionFromRecurly', function () {
  238. afterEach(function () {
  239. this.subscription.member_ids = []
  240. })
  241. it('should update the subscription with token etc when not expired', async function () {
  242. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  243. this.recurlySubscription,
  244. this.subscription,
  245. {}
  246. )
  247. this.subscription.recurlySubscription_id.should.equal(
  248. this.recurlySubscription.uuid
  249. )
  250. this.subscription.planCode.should.equal(
  251. this.recurlySubscription.plan.plan_code
  252. )
  253. this.subscription.save.called.should.equal(true)
  254. expect(
  255. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  256. ).to.have.been.calledWith(this.adminUser._id)
  257. })
  258. it('should remove the subscription when expired', async function () {
  259. this.recurlySubscription.state = 'expired'
  260. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  261. this.recurlySubscription,
  262. this.subscription,
  263. {}
  264. )
  265. this.SubscriptionModel.deleteOne.should.have.been.calledWith({
  266. _id: this.subscription._id,
  267. })
  268. })
  269. it('should not remove the subscription when expired if it has Managed Users enabled', async function () {
  270. this.Features.hasFeature.withArgs('saas').returns(true)
  271. this.subscription.managedUsersEnabled = true
  272. this.recurlySubscription.state = 'expired'
  273. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  274. this.recurlySubscription,
  275. this.subscription,
  276. {}
  277. )
  278. this.SubscriptionModel.deleteOne.should.not.have.been.called
  279. })
  280. it('should not remove the subscription when expired if it has Group SSO enabled', async function () {
  281. this.Features.hasFeature.withArgs('saas').returns(true)
  282. this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
  283. this.recurlySubscription.state = 'expired'
  284. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  285. this.recurlySubscription,
  286. this.subscription,
  287. {}
  288. )
  289. this.SubscriptionModel.deleteOne.should.not.have.been.called
  290. })
  291. it('should update all the users features', async function () {
  292. this.subscription.member_ids = this.allUserIds
  293. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  294. this.recurlySubscription,
  295. this.subscription,
  296. {}
  297. )
  298. expect(
  299. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  300. ).to.have.been.calledWith(this.adminUser._id)
  301. expect(
  302. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  303. ).to.have.been.calledWith(this.allUserIds[0])
  304. expect(
  305. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  306. ).to.have.been.calledWith(this.allUserIds[1])
  307. expect(
  308. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  309. ).to.have.been.calledWith(this.allUserIds[2])
  310. })
  311. it('should set group to true and save how many members can be added to group', async function () {
  312. this.recurlyPlan.groupPlan = true
  313. this.recurlyPlan.membersLimit = 5
  314. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  315. this.recurlySubscription,
  316. this.subscription,
  317. {}
  318. )
  319. this.subscription.membersLimit.should.equal(5)
  320. this.subscription.groupPlan.should.equal(true)
  321. this.subscription.member_ids.should.deep.equal([
  322. this.subscription.admin_id,
  323. ])
  324. })
  325. it('should delete and replace subscription when downgrading from group to individual plan', async function () {
  326. this.recurlyPlan.groupPlan = false
  327. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  328. this.recurlySubscription,
  329. this.groupSubscription,
  330. {}
  331. )
  332. })
  333. it('should not set group to true or set groupPlan', async function () {
  334. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  335. this.recurlySubscription,
  336. this.subscription,
  337. {}
  338. )
  339. assert.notEqual(this.subscription.membersLimit, 5)
  340. assert.notEqual(this.subscription.groupPlan, true)
  341. })
  342. describe('when the plan allows adding more seats', function () {
  343. beforeEach(function () {
  344. this.membersLimitAddOn = 'add_on1'
  345. this.recurlyPlan.groupPlan = true
  346. this.recurlyPlan.membersLimit = 5
  347. this.recurlyPlan.membersLimitAddOn = this.membersLimitAddOn
  348. })
  349. function expectMembersLimit(limit) {
  350. it('should set the membersLimit accordingly', async function () {
  351. await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
  352. this.recurlySubscription,
  353. this.subscription,
  354. {}
  355. )
  356. expect(this.subscription.membersLimit).to.equal(limit)
  357. })
  358. }
  359. describe('when the recurlySubscription does not have add ons', function () {
  360. beforeEach(function () {
  361. delete this.recurlySubscription.subscription_add_ons
  362. })
  363. expectMembersLimit(5)
  364. })
  365. describe('when the recurlySubscription has non-matching add ons', function () {
  366. beforeEach(function () {
  367. this.recurlySubscription.subscription_add_ons = [
  368. { add_on_code: 'add_on_99', quantity: 3 },
  369. ]
  370. })
  371. expectMembersLimit(5)
  372. })
  373. describe('when the recurlySubscription has a matching add on', function () {
  374. beforeEach(function () {
  375. this.recurlySubscription.subscription_add_ons = [
  376. { add_on_code: this.membersLimitAddOn, quantity: 10 },
  377. ]
  378. })
  379. expectMembersLimit(15)
  380. })
  381. // NOTE: This is unexpected, but we are going to support it anyways.
  382. describe('when the recurlySubscription has multiple matching add ons', function () {
  383. beforeEach(function () {
  384. this.recurlySubscription.subscription_add_ons = [
  385. { add_on_code: this.membersLimitAddOn, quantity: 10 },
  386. { add_on_code: this.membersLimitAddOn, quantity: 3 },
  387. ]
  388. })
  389. expectMembersLimit(18)
  390. })
  391. })
  392. })
  393. describe('addUserToGroup', function () {
  394. it('should add the user ids to the group as a set', async function () {
  395. this.SubscriptionModel.findOne = sinon
  396. .stub()
  397. .resolves(this.groupSubscription)
  398. await this.SubscriptionUpdater.promises.addUserToGroup(
  399. this.groupSubscription._id,
  400. this.otherUserId
  401. )
  402. const searchOps = { _id: this.groupSubscription._id }
  403. const insertOperation = {
  404. $addToSet: { member_ids: this.otherUserId },
  405. }
  406. this.SubscriptionModel.updateOne
  407. .calledWith(searchOps, insertOperation)
  408. .should.equal(true)
  409. sinon.assert.calledWith(
  410. this.AnalyticsManager.recordEventForUserInBackground,
  411. this.otherUserId,
  412. 'group-subscription-joined',
  413. {
  414. groupId: this.groupSubscription._id,
  415. subscriptionId: this.groupSubscription.recurlySubscription_id,
  416. }
  417. )
  418. })
  419. it('should update the users features', async function () {
  420. await this.SubscriptionUpdater.promises.addUserToGroup(
  421. this.subscription._id,
  422. this.otherUserId
  423. )
  424. this.FeaturesUpdater.promises.refreshFeatures
  425. .calledWith(this.otherUserId)
  426. .should.equal(true)
  427. })
  428. it('should set the group plan code user property to the best plan with 1 group subscription', async function () {
  429. this.SubscriptionLocator.promises.getMemberSubscriptions
  430. .withArgs(this.otherUserId)
  431. .resolves([this.groupSubscription])
  432. await this.SubscriptionUpdater.promises.addUserToGroup(
  433. this.groupSubscription._id,
  434. this.otherUserId
  435. )
  436. sinon.assert.calledWith(
  437. this.AnalyticsManager.setUserPropertyForUserInBackground,
  438. this.otherUserId,
  439. 'group-subscription-plan-code',
  440. 'group_subscription'
  441. )
  442. })
  443. it('should set the group plan code user property to the best plan with 2 group subscriptions', async function () {
  444. this.SubscriptionLocator.promises.getMemberSubscriptions
  445. .withArgs(this.otherUserId)
  446. .resolves([this.groupSubscription, this.betterGroupSubscription])
  447. await this.SubscriptionUpdater.promises.addUserToGroup(
  448. this.betterGroupSubscription._id,
  449. this.otherUserId
  450. )
  451. sinon.assert.calledWith(
  452. this.AnalyticsManager.setUserPropertyForUserInBackground,
  453. this.otherUserId,
  454. 'group-subscription-plan-code',
  455. 'better_group_subscription'
  456. )
  457. })
  458. it('should set the group plan code user property to the best plan with 2 group subscriptions in reverse order', async function () {
  459. this.SubscriptionLocator.promises.getMemberSubscriptions
  460. .withArgs(this.otherUserId)
  461. .resolves([this.betterGroupSubscription, this.groupSubscription])
  462. await this.SubscriptionUpdater.promises.addUserToGroup(
  463. this.betterGroupSubscription._id,
  464. this.otherUserId
  465. )
  466. sinon.assert.calledWith(
  467. this.AnalyticsManager.setUserPropertyForUserInBackground,
  468. this.otherUserId,
  469. 'group-subscription-plan-code',
  470. 'better_group_subscription'
  471. )
  472. })
  473. it('should add an entry to the user audit log when joining a group', async function () {
  474. await this.SubscriptionUpdater.promises.addUserToGroup(
  475. this.subscription._id,
  476. this.otherUserId
  477. )
  478. sinon.assert.calledWith(
  479. this.UserAuditLogHandler.promises.addEntry,
  480. this.otherUserId,
  481. 'join-group-subscription',
  482. undefined,
  483. undefined,
  484. {
  485. subscriptionId: this.subscription._id,
  486. }
  487. )
  488. })
  489. })
  490. describe('removeUserFromGroup', function () {
  491. beforeEach(function () {
  492. this.fakeSubscriptions = [
  493. {
  494. _id: 'fake-id-1',
  495. },
  496. {
  497. _id: 'fake-id-2',
  498. },
  499. ]
  500. this.SubscriptionModel.findOne.resolves(this.groupSubscription)
  501. this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
  502. this.fakeSubscriptions
  503. )
  504. })
  505. it('should pull the users id from the group', async function () {
  506. await this.SubscriptionUpdater.promises.removeUserFromGroup(
  507. this.subscription._id,
  508. this.otherUserId
  509. )
  510. const removeOperation = { $pull: { member_ids: this.otherUserId } }
  511. this.SubscriptionModel.updateOne
  512. .calledWith({ _id: this.subscription._id }, removeOperation)
  513. .should.equal(true)
  514. })
  515. it('should send a group-subscription-left event', async function () {
  516. await this.SubscriptionUpdater.promises.removeUserFromGroup(
  517. this.groupSubscription._id,
  518. this.otherUserId
  519. )
  520. sinon.assert.calledWith(
  521. this.AnalyticsManager.recordEventForUserInBackground,
  522. this.otherUserId,
  523. 'group-subscription-left',
  524. {
  525. groupId: this.groupSubscription._id,
  526. subscriptionId: this.groupSubscription.recurlySubscription_id,
  527. }
  528. )
  529. })
  530. it('should set the group plan code user property when removing user from group', async function () {
  531. await this.SubscriptionUpdater.promises.removeUserFromGroup(
  532. this.subscription._id,
  533. this.otherUserId
  534. )
  535. sinon.assert.calledWith(
  536. this.AnalyticsManager.setUserPropertyForUserInBackground,
  537. this.otherUserId,
  538. 'group-subscription-plan-code',
  539. null
  540. )
  541. })
  542. it('should update the users features', async function () {
  543. await this.SubscriptionUpdater.promises.removeUserFromGroup(
  544. this.subscription._id,
  545. this.otherUserId
  546. )
  547. this.FeaturesUpdater.promises.refreshFeatures
  548. .calledWith(this.otherUserId)
  549. .should.equal(true)
  550. })
  551. it('should add an audit log when a user leaves a group', async function () {
  552. await this.SubscriptionUpdater.promises.removeUserFromGroup(
  553. this.subscription._id,
  554. this.otherUserId
  555. )
  556. sinon.assert.calledWith(
  557. this.UserAuditLogHandler.promises.addEntry,
  558. this.otherUserId,
  559. 'leave-group-subscription',
  560. undefined,
  561. undefined,
  562. {
  563. subscriptionId: this.subscription._id,
  564. }
  565. )
  566. })
  567. })
  568. describe('removeUserFromAllGroups', function () {
  569. beforeEach(function () {
  570. this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([
  571. {
  572. _id: 'fake-id-1',
  573. },
  574. {
  575. _id: 'fake-id-2',
  576. },
  577. ])
  578. })
  579. it('should set the group plan code user property when removing user from all groups', async function () {
  580. await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
  581. this.otherUserId
  582. )
  583. sinon.assert.calledWith(
  584. this.AnalyticsManager.setUserPropertyForUserInBackground,
  585. this.otherUserId,
  586. 'group-subscription-plan-code',
  587. null
  588. )
  589. })
  590. it('should pull the users id from all groups', async function () {
  591. await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
  592. this.otherUserId
  593. )
  594. const filter = { _id: ['fake-id-1', 'fake-id-2'] }
  595. const removeOperation = { $pull: { member_ids: this.otherUserId } }
  596. sinon.assert.calledWith(
  597. this.SubscriptionModel.updateMany,
  598. filter,
  599. removeOperation
  600. )
  601. })
  602. it('should send a group-subscription-left event for each group', async function () {
  603. this.fakeSub1 = {
  604. _id: 'fake-id-1',
  605. groupPlan: true,
  606. recurlySubscription_id: 'fake-sub-1',
  607. }
  608. this.fakeSub2 = {
  609. _id: 'fake-id-2',
  610. groupPlan: true,
  611. recurlySubscription_id: 'fake-sub-2',
  612. }
  613. this.SubscriptionModel.findOne
  614. .withArgs(
  615. { _id: 'fake-id-1' },
  616. { recurlySubscription_id: 1, groupPlan: 1 }
  617. )
  618. .resolves(this.fakeSub1)
  619. .withArgs(
  620. { _id: 'fake-id-2' },
  621. { recurlySubscription_id: 1, groupPlan: 1 }
  622. )
  623. .resolves(this.fakeSub2)
  624. await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
  625. this.otherUserId
  626. )
  627. sinon.assert.calledWith(
  628. this.AnalyticsManager.recordEventForUserInBackground,
  629. this.otherUserId,
  630. 'group-subscription-left',
  631. {
  632. groupId: 'fake-id-1',
  633. subscriptionId: 'fake-sub-1',
  634. }
  635. )
  636. sinon.assert.calledWith(
  637. this.AnalyticsManager.recordEventForUserInBackground,
  638. this.otherUserId,
  639. 'group-subscription-left',
  640. {
  641. groupId: 'fake-id-2',
  642. subscriptionId: 'fake-sub-2',
  643. }
  644. )
  645. })
  646. it('should add an audit log entry for each group the user leaves', async function () {
  647. await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
  648. this.otherUserId
  649. )
  650. sinon.assert.calledWith(
  651. this.UserAuditLogHandler.promises.addEntry,
  652. this.otherUserId,
  653. 'leave-group-subscription',
  654. undefined,
  655. undefined,
  656. {
  657. subscriptionId: 'fake-id-1',
  658. }
  659. )
  660. sinon.assert.calledWith(
  661. this.UserAuditLogHandler.promises.addEntry,
  662. this.otherUserId,
  663. 'leave-group-subscription',
  664. undefined,
  665. undefined,
  666. {
  667. subscriptionId: 'fake-id-2',
  668. }
  669. )
  670. })
  671. })
  672. describe('deleteSubscription', function () {
  673. beforeEach(async function () {
  674. this.subscription = {
  675. _id: new ObjectId().toString(),
  676. mock: 'subscription',
  677. admin_id: new ObjectId(),
  678. member_ids: [new ObjectId(), new ObjectId(), new ObjectId()],
  679. }
  680. await this.SubscriptionUpdater.promises.deleteSubscription(
  681. this.subscription,
  682. {}
  683. )
  684. })
  685. it('should remove the subscription', function () {
  686. this.SubscriptionModel.deleteOne
  687. .calledWith({ _id: this.subscription._id })
  688. .should.equal(true)
  689. })
  690. it('should downgrade the admin_id', function () {
  691. expect(
  692. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  693. ).to.have.been.calledWith(this.subscription.admin_id)
  694. })
  695. it('should downgrade all of the members', function () {
  696. for (const userId of this.subscription.member_ids) {
  697. expect(
  698. this.FeaturesUpdater.promises.scheduleRefreshFeatures
  699. ).to.have.been.calledWith(userId)
  700. }
  701. })
  702. })
  703. })