SubscriptionUpdaterTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. const SandboxedModule = require('sandboxed-module')
  2. const chai = require('chai')
  3. const sinon = require('sinon')
  4. const modulePath =
  5. '../../../../app/src/Features/Subscription/SubscriptionUpdater'
  6. const { assert } = require('chai')
  7. const { ObjectId } = require('mongodb')
  8. chai.should()
  9. describe('SubscriptionUpdater', function() {
  10. beforeEach(function() {
  11. this.recurlySubscription = {
  12. uuid: '1238uoijdasjhd',
  13. plan: {
  14. plan_code: 'kjhsakjds'
  15. }
  16. }
  17. this.adminUser = { _id: (this.adminuser_id = '5208dd34438843e2db000007') }
  18. this.otherUserId = '5208dd34438842e2db000005'
  19. this.allUserIds = ['13213', 'dsadas', 'djsaiud89']
  20. this.subscription = {
  21. _id: '111111111111111111111111',
  22. admin_id: this.adminUser._id,
  23. manager_ids: [this.adminUser._id],
  24. member_ids: [],
  25. save: sinon.stub().callsArgWith(0),
  26. planCode: 'student_or_something'
  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().callsArgWith(0),
  35. planCode: 'group_subscription'
  36. }
  37. this.updateStub = sinon.stub().callsArgWith(2, null)
  38. this.updateManyStub = sinon.stub().callsArgWith(2, null)
  39. this.findAndModifyStub = sinon
  40. .stub()
  41. .callsArgWith(2, null, this.subscription)
  42. this.findOneAndUpdateStub = sinon
  43. .stub()
  44. .callsArgWith(2, null, this.subscription)
  45. let subscription = this.subscription
  46. this.SubscriptionModel = class {
  47. constructor(opts) {
  48. // Always return our mock subscription when creating a new one
  49. subscription.admin_id = opts.admin_id
  50. subscription.manager_ids = [opts.admin_id]
  51. return subscription
  52. }
  53. }
  54. this.SubscriptionModel.remove = sinon.stub().yields()
  55. this.SubscriptionModel.update = this.updateStub
  56. this.SubscriptionModel.updateMany = this.updateManyStub
  57. this.SubscriptionModel.findAndModify = this.findAndModifyStub
  58. this.SubscriptionModel.findOneAndUpdate = this.findOneAndUpdateStub
  59. this.SubscriptionLocator = {
  60. getUsersSubscription: sinon.stub(),
  61. getGroupSubscriptionMemberOf: sinon.stub(),
  62. getMemberSubscriptions: sinon.stub().yields(null, [])
  63. }
  64. this.Settings = {
  65. defaultPlanCode: 'personal',
  66. defaultFeatures: { default: 'features' }
  67. }
  68. this.UserFeaturesUpdater = { updateFeatures: sinon.stub().yields() }
  69. this.PlansLocator = { findLocalPlanInSettings: sinon.stub().returns({}) }
  70. this.UserGetter = {
  71. getUsers(memberIds, projection, callback) {
  72. const users = memberIds.map(id => ({ _id: id }))
  73. callback(null, users)
  74. },
  75. getUser: sinon.stub()
  76. }
  77. this.ReferalFeatures = { getBonusFeatures: sinon.stub().callsArgWith(1) }
  78. this.Modules = { hooks: { fire: sinon.stub().callsArgWith(2, null, null) } }
  79. this.FeaturesUpdater = {
  80. refreshFeatures: sinon.stub().yields()
  81. }
  82. this.DeletedSubscription = {
  83. findOneAndUpdate: sinon.stub().yields()
  84. }
  85. this.SubscriptionUpdater = SandboxedModule.require(modulePath, {
  86. globals: {
  87. console: console
  88. },
  89. requires: {
  90. mongodb: { ObjectId },
  91. '../../models/Subscription': {
  92. Subscription: this.SubscriptionModel
  93. },
  94. './UserFeaturesUpdater': this.UserFeaturesUpdater,
  95. './SubscriptionLocator': this.SubscriptionLocator,
  96. '../User/UserGetter': this.UserGetter,
  97. './PlansLocator': this.PlansLocator,
  98. 'logger-sharelatex': {
  99. log() {},
  100. warn() {}
  101. },
  102. 'settings-sharelatex': this.Settings,
  103. '../../infrastructure/mongodb': { db: {}, ObjectId },
  104. './FeaturesUpdater': this.FeaturesUpdater,
  105. '../../models/DeletedSubscription': {
  106. DeletedSubscription: this.DeletedSubscription
  107. }
  108. }
  109. })
  110. })
  111. describe('updateAdmin', function() {
  112. it('should update the subscription admin', function(done) {
  113. this.subscription.groupPlan = true
  114. this.SubscriptionUpdater.updateAdmin(
  115. this.subscription,
  116. this.otherUserId,
  117. err => {
  118. if (err != null) {
  119. return done(err)
  120. }
  121. const query = {
  122. _id: ObjectId(this.subscription._id),
  123. customAccount: true
  124. }
  125. const update = {
  126. $set: { admin_id: ObjectId(this.otherUserId) },
  127. $addToSet: { manager_ids: ObjectId(this.otherUserId) }
  128. }
  129. this.SubscriptionModel.update.should.have.been.calledOnce
  130. this.SubscriptionModel.update.should.have.been.calledWith(
  131. query,
  132. update
  133. )
  134. done()
  135. }
  136. )
  137. })
  138. it('should remove the manager for non-group subscriptions', function(done) {
  139. this.SubscriptionUpdater.updateAdmin(
  140. this.subscription,
  141. this.otherUserId,
  142. err => {
  143. if (err != null) {
  144. return done(err)
  145. }
  146. const query = {
  147. _id: ObjectId(this.subscription._id),
  148. customAccount: true
  149. }
  150. const update = {
  151. $set: {
  152. admin_id: ObjectId(this.otherUserId),
  153. manager_ids: [ObjectId(this.otherUserId)]
  154. }
  155. }
  156. this.SubscriptionModel.update.should.have.been.calledOnce
  157. this.SubscriptionModel.update.should.have.been.calledWith(
  158. query,
  159. update
  160. )
  161. done()
  162. }
  163. )
  164. })
  165. })
  166. describe('syncSubscription', function() {
  167. beforeEach(function() {
  168. this.SubscriptionLocator.getUsersSubscription.callsArgWith(
  169. 1,
  170. null,
  171. this.subscription
  172. )
  173. this.SubscriptionUpdater._updateSubscriptionFromRecurly = sinon
  174. .stub()
  175. .yields()
  176. })
  177. it('should update the subscription if the user already is admin of one', function(done) {
  178. this.SubscriptionUpdater._createNewSubscription = sinon.stub()
  179. this.SubscriptionUpdater.syncSubscription(
  180. this.recurlySubscription,
  181. this.adminUser._id,
  182. err => {
  183. if (err != null) {
  184. return done(err)
  185. }
  186. this.SubscriptionLocator.getUsersSubscription
  187. .calledWith(this.adminUser._id)
  188. .should.equal(true)
  189. this.SubscriptionUpdater._updateSubscriptionFromRecurly.called.should.equal(
  190. true
  191. )
  192. this.SubscriptionUpdater._updateSubscriptionFromRecurly
  193. .calledWith(this.recurlySubscription, this.subscription)
  194. .should.equal(true)
  195. done()
  196. }
  197. )
  198. })
  199. it('should not call updateFeatures with group subscription if recurly subscription is not expired', function(done) {
  200. this.SubscriptionUpdater.syncSubscription(
  201. this.recurlySubscription,
  202. this.adminUser._id,
  203. err => {
  204. if (err != null) {
  205. return done(err)
  206. }
  207. this.SubscriptionLocator.getUsersSubscription
  208. .calledWith(this.adminUser._id)
  209. .should.equal(true)
  210. this.SubscriptionUpdater._updateSubscriptionFromRecurly.called.should.equal(
  211. true
  212. )
  213. this.SubscriptionUpdater._updateSubscriptionFromRecurly
  214. .calledWith(this.recurlySubscription, this.subscription)
  215. .should.equal(true)
  216. this.UserFeaturesUpdater.updateFeatures.called.should.equal(false)
  217. done()
  218. }
  219. )
  220. })
  221. })
  222. describe('_updateSubscriptionFromRecurly', function() {
  223. beforeEach(function() {
  224. this.FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
  225. this.SubscriptionUpdater.deleteSubscription = sinon.stub().yields()
  226. })
  227. afterEach(function() {
  228. this.subscription.member_ids = []
  229. })
  230. it('should update the subscription with token etc when not expired', function(done) {
  231. this.SubscriptionUpdater._updateSubscriptionFromRecurly(
  232. this.recurlySubscription,
  233. this.subscription,
  234. {},
  235. err => {
  236. if (err != null) {
  237. return done(err)
  238. }
  239. this.subscription.recurlySubscription_id.should.equal(
  240. this.recurlySubscription.uuid
  241. )
  242. this.subscription.planCode.should.equal(
  243. this.recurlySubscription.plan.plan_code
  244. )
  245. this.subscription.save.called.should.equal(true)
  246. this.FeaturesUpdater.refreshFeatures
  247. .calledWith(this.adminUser._id)
  248. .should.equal(true)
  249. done()
  250. }
  251. )
  252. })
  253. it('should remove the subscription when expired', function(done) {
  254. this.recurlySubscription.state = 'expired'
  255. this.SubscriptionUpdater._updateSubscriptionFromRecurly(
  256. this.recurlySubscription,
  257. this.subscription,
  258. {},
  259. err => {
  260. if (err != null) {
  261. return done(err)
  262. }
  263. this.SubscriptionUpdater.deleteSubscription
  264. .calledWithMatch(this.subscription)
  265. .should.equal(true)
  266. done()
  267. }
  268. )
  269. })
  270. it('should update all the users features', function(done) {
  271. this.subscription.member_ids = this.allUserIds
  272. this.SubscriptionUpdater._updateSubscriptionFromRecurly(
  273. this.recurlySubscription,
  274. this.subscription,
  275. {},
  276. err => {
  277. if (err != null) {
  278. return done(err)
  279. }
  280. this.FeaturesUpdater.refreshFeatures
  281. .calledWith(this.adminUser._id)
  282. .should.equal(true)
  283. this.FeaturesUpdater.refreshFeatures
  284. .calledWith(this.allUserIds[0])
  285. .should.equal(true)
  286. this.FeaturesUpdater.refreshFeatures
  287. .calledWith(this.allUserIds[1])
  288. .should.equal(true)
  289. this.FeaturesUpdater.refreshFeatures
  290. .calledWith(this.allUserIds[2])
  291. .should.equal(true)
  292. done()
  293. }
  294. )
  295. })
  296. it('should set group to true and save how many members can be added to group', function(done) {
  297. this.PlansLocator.findLocalPlanInSettings
  298. .withArgs(this.recurlySubscription.plan.plan_code)
  299. .returns({ groupPlan: true, membersLimit: 5 })
  300. this.SubscriptionUpdater._updateSubscriptionFromRecurly(
  301. this.recurlySubscription,
  302. this.subscription,
  303. {},
  304. err => {
  305. if (err != null) {
  306. return done(err)
  307. }
  308. this.subscription.membersLimit.should.equal(5)
  309. this.subscription.groupPlan.should.equal(true)
  310. this.subscription.member_ids.should.deep.equal([
  311. this.subscription.admin_id
  312. ])
  313. done()
  314. }
  315. )
  316. })
  317. it('should not set group to true or set groupPlan', function(done) {
  318. this.SubscriptionUpdater._updateSubscriptionFromRecurly(
  319. this.recurlySubscription,
  320. this.subscription,
  321. {},
  322. err => {
  323. if (err != null) {
  324. return done(err)
  325. }
  326. assert.notEqual(this.subscription.membersLimit, 5)
  327. assert.notEqual(this.subscription.groupPlan, true)
  328. done()
  329. }
  330. )
  331. })
  332. })
  333. describe('_createNewSubscription', function() {
  334. it('should create a new subscription then update the subscription', function(done) {
  335. this.SubscriptionUpdater._createNewSubscription(
  336. this.adminUser._id,
  337. () => {
  338. this.subscription.admin_id.should.equal(this.adminUser._id)
  339. this.subscription.manager_ids.should.deep.equal([this.adminUser._id])
  340. this.subscription.save.called.should.equal(true)
  341. done()
  342. }
  343. )
  344. })
  345. })
  346. describe('addUserToGroup', function() {
  347. beforeEach(function() {
  348. this.SubscriptionUpdater.addUsersToGroup = sinon.stub().yields(null)
  349. })
  350. it('delegates to addUsersToGroup', function(done) {
  351. this.SubscriptionUpdater.addUserToGroup(
  352. this.subscription._id,
  353. this.otherUserId,
  354. () => {
  355. this.SubscriptionUpdater.addUsersToGroup
  356. .calledWith(this.subscription._id, [this.otherUserId])
  357. .should.equal(true)
  358. done()
  359. }
  360. )
  361. })
  362. })
  363. describe('addUsersToGroup', function() {
  364. beforeEach(function() {
  365. this.FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
  366. })
  367. it('should add the user ids to the group as a set', function(done) {
  368. this.SubscriptionUpdater.addUsersToGroup(
  369. this.subscription._id,
  370. [this.otherUserId],
  371. () => {
  372. const searchOps = { _id: this.subscription._id }
  373. const insertOperation = {
  374. $addToSet: { member_ids: { $each: [this.otherUserId] } }
  375. }
  376. this.findAndModifyStub
  377. .calledWith(searchOps, insertOperation)
  378. .should.equal(true)
  379. done()
  380. }
  381. )
  382. })
  383. it('should update the users features', function(done) {
  384. this.SubscriptionUpdater.addUserToGroup(
  385. this.subscription._id,
  386. this.otherUserId,
  387. () => {
  388. this.FeaturesUpdater.refreshFeatures
  389. .calledWith(this.otherUserId)
  390. .should.equal(true)
  391. done()
  392. }
  393. )
  394. })
  395. })
  396. describe('removeUserFromGroups', function() {
  397. beforeEach(function() {
  398. this.FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
  399. this.UserGetter.getUser.yields(null, {})
  400. this.fakeSubscriptions = [{ _id: 'fake-id-1' }, { _id: 'fake-id-2' }]
  401. this.SubscriptionLocator.getMemberSubscriptions.yields(
  402. null,
  403. this.fakeSubscriptions
  404. )
  405. })
  406. it('should pull the users id from the group', function(done) {
  407. this.SubscriptionUpdater.removeUserFromGroup(
  408. this.subscription._id,
  409. this.otherUserId,
  410. () => {
  411. const searchOps = { _id: this.subscription._id }
  412. const removeOperation = { $pull: { member_ids: this.otherUserId } }
  413. this.updateManyStub
  414. .calledWith(searchOps, removeOperation)
  415. .should.equal(true)
  416. done()
  417. }
  418. )
  419. })
  420. it('should pull the users id from all groups', function(done) {
  421. this.SubscriptionUpdater.removeUserFromAllGroups(this.otherUserId, () => {
  422. const filter = { _id: ['fake-id-1', 'fake-id-2'] }
  423. const removeOperation = { $pull: { member_ids: this.otherUserId } }
  424. sinon.assert.calledWith(this.updateManyStub, filter, removeOperation)
  425. done()
  426. })
  427. })
  428. it('should update the users features', function(done) {
  429. this.SubscriptionUpdater.removeUserFromGroup(
  430. this.subscription._id,
  431. this.otherUserId,
  432. () => {
  433. this.FeaturesUpdater.refreshFeatures
  434. .calledWith(this.otherUserId)
  435. .should.equal(true)
  436. done()
  437. }
  438. )
  439. })
  440. })
  441. describe('deleteSubscription', function() {
  442. beforeEach(function(done) {
  443. this.subscription = {
  444. _id: ObjectId().toString(),
  445. mock: 'subscription',
  446. admin_id: ObjectId(),
  447. member_ids: [ObjectId(), ObjectId(), ObjectId()]
  448. }
  449. this.SubscriptionLocator.getSubscription = sinon
  450. .stub()
  451. .yields(null, this.subscription)
  452. this.FeaturesUpdater.refreshFeatures = sinon.stub().yields()
  453. this.SubscriptionUpdater.deleteSubscription(this.subscription, {}, done)
  454. })
  455. it('should remove the subscription', function() {
  456. this.SubscriptionModel.remove
  457. .calledWith({ _id: this.subscription._id })
  458. .should.equal(true)
  459. })
  460. it('should downgrade the admin_id', function() {
  461. this.FeaturesUpdater.refreshFeatures
  462. .calledWith(this.subscription.admin_id)
  463. .should.equal(true)
  464. })
  465. it('should downgrade all of the members', function() {
  466. for (const userId of this.subscription.member_ids) {
  467. this.FeaturesUpdater.refreshFeatures
  468. .calledWith(userId)
  469. .should.equal(true)
  470. }
  471. })
  472. })
  473. })