| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765 |
- const SandboxedModule = require('sandboxed-module')
- const sinon = require('sinon')
- const modulePath =
- '../../../../app/src/Features/Subscription/SubscriptionUpdater'
- const { assert, expect } = require('chai')
- const { ObjectId } = require('mongodb-legacy')
- describe('SubscriptionUpdater', function () {
- beforeEach(function () {
- this.recurlyPlan = { planCode: 'recurly-plan' }
- this.recurlySubscription = {
- uuid: '1238uoijdasjhd',
- plan: {
- plan_code: this.recurlyPlan.planCode,
- },
- }
- this.adminUser = { _id: (this.adminuser_id = '5208dd34438843e2db000007') }
- this.otherUserId = '5208dd34438842e2db000005'
- this.allUserIds = ['13213', 'dsadas', 'djsaiud89']
- this.subscription = {
- _id: '111111111111111111111111',
- admin_id: this.adminUser._id,
- manager_ids: [this.adminUser._id],
- member_ids: [],
- save: sinon.stub().resolves(),
- planCode: 'student_or_something',
- recurlySubscription_id: 'abc123def456fab789',
- }
- this.user_id = this.adminuser_id
- this.groupSubscription = {
- _id: '222222222222222222222222',
- admin_id: this.adminUser._id,
- manager_ids: [this.adminUser._id],
- member_ids: this.allUserIds,
- save: sinon.stub().resolves(),
- groupPlan: true,
- planCode: 'group_subscription',
- recurlySubscription_id: '456fab789abc123def',
- }
- this.betterGroupSubscription = {
- _id: '999999999999999999999999',
- admin_id: this.adminUser._id,
- manager_ids: [this.adminUser._id],
- member_ids: [this.otherUserId],
- save: sinon.stub().resolves(),
- groupPlan: true,
- planCode: 'better_group_subscription',
- recurlySubscription_id: '123def456fab789abc',
- }
- const subscription = this.subscription
- this.SubscriptionModel = class {
- constructor(opts) {
- // Always return our mock subscription when creating a new one
- subscription.admin_id = opts.admin_id
- subscription.manager_ids = [opts.admin_id]
- return subscription
- }
- save() {
- return Promise.resolve(subscription)
- }
- }
- this.SubscriptionModel.deleteOne = sinon
- .stub()
- .returns({ exec: sinon.stub().resolves() })
- this.SubscriptionModel.updateOne = sinon
- .stub()
- .returns({ exec: sinon.stub().resolves() })
- this.SubscriptionModel.findOne = sinon.stub().resolves()
- this.SubscriptionModel.updateMany = sinon
- .stub()
- .returns({ exec: sinon.stub().resolves() })
- this.SubscriptionModel.findOneAndUpdate = sinon.stub().returns({
- exec: sinon.stub().resolves(this.subscription),
- })
- this.SSOConfigModel = class {}
- this.SSOConfigModel.findOne = sinon.stub().returns({
- lean: sinon.stub().returns({
- exec: sinon.stub().resolves({ enabled: true }),
- }),
- })
- this.SubscriptionLocator = {
- promises: {
- getUsersSubscription: sinon.stub(),
- getGroupSubscriptionMemberOf: sinon.stub(),
- getMemberSubscriptions: sinon.stub().resolves([]),
- getSubscription: sinon.stub(),
- },
- }
- this.SubscriptionLocator.promises.getSubscription
- .withArgs(this.subscription._id)
- .resolves(this.subscription)
- this.Settings = {
- defaultPlanCode: 'personal',
- defaultFeatures: { default: 'features' },
- plans: [
- this.recurlyPlan,
- { planCode: this.subscription.planCode, features: {} },
- {
- planCode: this.groupSubscription.planCode,
- features: {
- collaborators: 10,
- compileTimeout: 60,
- dropbox: true,
- },
- },
- {
- planCode: this.betterGroupSubscription.planCode,
- features: {
- collaborators: -1,
- compileTimeout: 240,
- dropbox: true,
- },
- },
- ],
- }
- this.UserFeaturesUpdater = {
- promises: {
- updateFeatures: sinon.stub().resolves(),
- },
- }
- this.ReferalFeatures = {
- promises: {
- getBonusFeatures: sinon.stub().resolves(),
- },
- }
- this.FeaturesUpdater = {
- promises: {
- scheduleRefreshFeatures: sinon.stub().resolves(),
- refreshFeatures: sinon.stub().resolves({}),
- },
- }
- this.DeletedSubscription = {
- findOneAndUpdate: sinon.stub().returns({ exec: sinon.stub().resolves() }),
- }
- this.AnalyticsManager = {
- recordEventForUserInBackground: sinon.stub().resolves(),
- setUserPropertyForUserInBackground: sinon.stub(),
- }
- this.Features = {
- hasFeature: sinon.stub().returns(false),
- }
- this.UserAuditLogHandler = {
- promises: {
- addEntry: sinon.stub().resolves(),
- },
- }
- this.SubscriptionUpdater = SandboxedModule.require(modulePath, {
- requires: {
- '../../models/Subscription': {
- Subscription: this.SubscriptionModel,
- },
- '../../models/SSOConfig': { SSOConfig: this.SSOConfigModel },
- './UserFeaturesUpdater': this.UserFeaturesUpdater,
- './SubscriptionLocator': this.SubscriptionLocator,
- '@overleaf/settings': this.Settings,
- '../../infrastructure/mongodb': { db: {}, ObjectId },
- './FeaturesUpdater': this.FeaturesUpdater,
- '../../models/DeletedSubscription': {
- DeletedSubscription: this.DeletedSubscription,
- },
- '../Analytics/AnalyticsManager': this.AnalyticsManager,
- '../../infrastructure/Features': this.Features,
- '../User/UserAuditLogHandler': this.UserAuditLogHandler,
- },
- })
- })
- describe('updateAdmin', function () {
- it('should update the subscription admin', async function () {
- this.subscription.groupPlan = true
- await this.SubscriptionUpdater.promises.updateAdmin(
- this.subscription,
- this.otherUserId
- )
- const query = {
- _id: new ObjectId(this.subscription._id),
- customAccount: true,
- }
- const update = {
- $set: { admin_id: new ObjectId(this.otherUserId) },
- $addToSet: { manager_ids: new ObjectId(this.otherUserId) },
- }
- this.SubscriptionModel.updateOne.should.have.been.calledOnce
- this.SubscriptionModel.updateOne.should.have.been.calledWith(
- query,
- update
- )
- })
- it('should remove the manager for non-group subscriptions', async function () {
- await this.SubscriptionUpdater.promises.updateAdmin(
- this.subscription,
- this.otherUserId
- )
- const query = {
- _id: new ObjectId(this.subscription._id),
- customAccount: true,
- }
- const update = {
- $set: {
- admin_id: new ObjectId(this.otherUserId),
- manager_ids: [new ObjectId(this.otherUserId)],
- },
- }
- this.SubscriptionModel.updateOne.should.have.been.calledOnce
- this.SubscriptionModel.updateOne.should.have.been.calledWith(
- query,
- update
- )
- })
- })
- describe('syncSubscription', function () {
- beforeEach(function () {
- this.SubscriptionLocator.promises.getUsersSubscription.resolves(
- this.subscription
- )
- })
- it('should update the subscription if the user already is admin of one', async function () {
- await this.SubscriptionUpdater.promises.syncSubscription(
- this.recurlySubscription,
- this.adminUser._id
- )
- this.SubscriptionLocator.promises.getUsersSubscription
- .calledWith(this.adminUser._id)
- .should.equal(true)
- })
- it('should not call updateFeatures with group subscription if recurly subscription is not expired', async function () {
- await this.SubscriptionUpdater.promises.syncSubscription(
- this.recurlySubscription,
- this.adminUser._id
- )
- this.SubscriptionLocator.promises.getUsersSubscription
- .calledWith(this.adminUser._id)
- .should.equal(true)
- this.UserFeaturesUpdater.promises.updateFeatures.called.should.equal(
- false
- )
- })
- })
- describe('updateSubscriptionFromRecurly', function () {
- afterEach(function () {
- this.subscription.member_ids = []
- })
- it('should update the subscription with token etc when not expired', async function () {
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- this.subscription.recurlySubscription_id.should.equal(
- this.recurlySubscription.uuid
- )
- this.subscription.planCode.should.equal(
- this.recurlySubscription.plan.plan_code
- )
- this.subscription.save.called.should.equal(true)
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.adminUser._id)
- })
- it('should remove the subscription when expired', async function () {
- this.recurlySubscription.state = 'expired'
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- this.SubscriptionModel.deleteOne.should.have.been.calledWith({
- _id: this.subscription._id,
- })
- })
- it('should not remove the subscription when expired if it has Managed Users enabled', async function () {
- this.Features.hasFeature.withArgs('saas').returns(true)
- this.subscription.managedUsersEnabled = true
- this.recurlySubscription.state = 'expired'
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- this.SubscriptionModel.deleteOne.should.not.have.been.called
- })
- it('should not remove the subscription when expired if it has Group SSO enabled', async function () {
- this.Features.hasFeature.withArgs('saas').returns(true)
- this.subscription.ssoConfig = new ObjectId('abc123abc123abc123abc123')
- this.recurlySubscription.state = 'expired'
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- this.SubscriptionModel.deleteOne.should.not.have.been.called
- })
- it('should update all the users features', async function () {
- this.subscription.member_ids = this.allUserIds
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.adminUser._id)
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.allUserIds[0])
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.allUserIds[1])
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.allUserIds[2])
- })
- it('should set group to true and save how many members can be added to group', async function () {
- this.recurlyPlan.groupPlan = true
- this.recurlyPlan.membersLimit = 5
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- this.subscription.membersLimit.should.equal(5)
- this.subscription.groupPlan.should.equal(true)
- this.subscription.member_ids.should.deep.equal([
- this.subscription.admin_id,
- ])
- })
- it('should delete and replace subscription when downgrading from group to individual plan', async function () {
- this.recurlyPlan.groupPlan = false
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.groupSubscription,
- {}
- )
- })
- it('should not set group to true or set groupPlan', async function () {
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- assert.notEqual(this.subscription.membersLimit, 5)
- assert.notEqual(this.subscription.groupPlan, true)
- })
- describe('when the plan allows adding more seats', function () {
- beforeEach(function () {
- this.membersLimitAddOn = 'add_on1'
- this.recurlyPlan.groupPlan = true
- this.recurlyPlan.membersLimit = 5
- this.recurlyPlan.membersLimitAddOn = this.membersLimitAddOn
- })
- function expectMembersLimit(limit) {
- it('should set the membersLimit accordingly', async function () {
- await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
- this.recurlySubscription,
- this.subscription,
- {}
- )
- expect(this.subscription.membersLimit).to.equal(limit)
- })
- }
- describe('when the recurlySubscription does not have add ons', function () {
- beforeEach(function () {
- delete this.recurlySubscription.subscription_add_ons
- })
- expectMembersLimit(5)
- })
- describe('when the recurlySubscription has non-matching add ons', function () {
- beforeEach(function () {
- this.recurlySubscription.subscription_add_ons = [
- { add_on_code: 'add_on_99', quantity: 3 },
- ]
- })
- expectMembersLimit(5)
- })
- describe('when the recurlySubscription has a matching add on', function () {
- beforeEach(function () {
- this.recurlySubscription.subscription_add_ons = [
- { add_on_code: this.membersLimitAddOn, quantity: 10 },
- ]
- })
- expectMembersLimit(15)
- })
- // NOTE: This is unexpected, but we are going to support it anyways.
- describe('when the recurlySubscription has multiple matching add ons', function () {
- beforeEach(function () {
- this.recurlySubscription.subscription_add_ons = [
- { add_on_code: this.membersLimitAddOn, quantity: 10 },
- { add_on_code: this.membersLimitAddOn, quantity: 3 },
- ]
- })
- expectMembersLimit(18)
- })
- })
- })
- describe('addUserToGroup', function () {
- it('should add the user ids to the group as a set', async function () {
- this.SubscriptionModel.findOne = sinon
- .stub()
- .resolves(this.groupSubscription)
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.groupSubscription._id,
- this.otherUserId
- )
- const searchOps = { _id: this.groupSubscription._id }
- const insertOperation = {
- $addToSet: { member_ids: this.otherUserId },
- }
- this.SubscriptionModel.updateOne
- .calledWith(searchOps, insertOperation)
- .should.equal(true)
- sinon.assert.calledWith(
- this.AnalyticsManager.recordEventForUserInBackground,
- this.otherUserId,
- 'group-subscription-joined',
- {
- groupId: this.groupSubscription._id,
- subscriptionId: this.groupSubscription.recurlySubscription_id,
- }
- )
- })
- it('should update the users features', async function () {
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.subscription._id,
- this.otherUserId
- )
- this.FeaturesUpdater.promises.refreshFeatures
- .calledWith(this.otherUserId)
- .should.equal(true)
- })
- it('should set the group plan code user property to the best plan with 1 group subscription', async function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions
- .withArgs(this.otherUserId)
- .resolves([this.groupSubscription])
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.groupSubscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.setUserPropertyForUserInBackground,
- this.otherUserId,
- 'group-subscription-plan-code',
- 'group_subscription'
- )
- })
- it('should set the group plan code user property to the best plan with 2 group subscriptions', async function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions
- .withArgs(this.otherUserId)
- .resolves([this.groupSubscription, this.betterGroupSubscription])
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.betterGroupSubscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.setUserPropertyForUserInBackground,
- this.otherUserId,
- 'group-subscription-plan-code',
- 'better_group_subscription'
- )
- })
- it('should set the group plan code user property to the best plan with 2 group subscriptions in reverse order', async function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions
- .withArgs(this.otherUserId)
- .resolves([this.betterGroupSubscription, this.groupSubscription])
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.betterGroupSubscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.setUserPropertyForUserInBackground,
- this.otherUserId,
- 'group-subscription-plan-code',
- 'better_group_subscription'
- )
- })
- it('should add an entry to the user audit log when joining a group', async function () {
- await this.SubscriptionUpdater.promises.addUserToGroup(
- this.subscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.UserAuditLogHandler.promises.addEntry,
- this.otherUserId,
- 'join-group-subscription',
- undefined,
- undefined,
- {
- subscriptionId: this.subscription._id,
- }
- )
- })
- })
- describe('removeUserFromGroup', function () {
- beforeEach(function () {
- this.fakeSubscriptions = [
- {
- _id: 'fake-id-1',
- },
- {
- _id: 'fake-id-2',
- },
- ]
- this.SubscriptionModel.findOne.resolves(this.groupSubscription)
- this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
- this.fakeSubscriptions
- )
- })
- it('should pull the users id from the group', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromGroup(
- this.subscription._id,
- this.otherUserId
- )
- const removeOperation = { $pull: { member_ids: this.otherUserId } }
- this.SubscriptionModel.updateOne
- .calledWith({ _id: this.subscription._id }, removeOperation)
- .should.equal(true)
- })
- it('should send a group-subscription-left event', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromGroup(
- this.groupSubscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.recordEventForUserInBackground,
- this.otherUserId,
- 'group-subscription-left',
- {
- groupId: this.groupSubscription._id,
- subscriptionId: this.groupSubscription.recurlySubscription_id,
- }
- )
- })
- it('should set the group plan code user property when removing user from group', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromGroup(
- this.subscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.setUserPropertyForUserInBackground,
- this.otherUserId,
- 'group-subscription-plan-code',
- null
- )
- })
- it('should update the users features', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromGroup(
- this.subscription._id,
- this.otherUserId
- )
- this.FeaturesUpdater.promises.refreshFeatures
- .calledWith(this.otherUserId)
- .should.equal(true)
- })
- it('should add an audit log when a user leaves a group', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromGroup(
- this.subscription._id,
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.UserAuditLogHandler.promises.addEntry,
- this.otherUserId,
- 'leave-group-subscription',
- undefined,
- undefined,
- {
- subscriptionId: this.subscription._id,
- }
- )
- })
- })
- describe('removeUserFromAllGroups', function () {
- beforeEach(function () {
- this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([
- {
- _id: 'fake-id-1',
- },
- {
- _id: 'fake-id-2',
- },
- ])
- })
- it('should set the group plan code user property when removing user from all groups', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.setUserPropertyForUserInBackground,
- this.otherUserId,
- 'group-subscription-plan-code',
- null
- )
- })
- it('should pull the users id from all groups', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
- this.otherUserId
- )
- const filter = { _id: ['fake-id-1', 'fake-id-2'] }
- const removeOperation = { $pull: { member_ids: this.otherUserId } }
- sinon.assert.calledWith(
- this.SubscriptionModel.updateMany,
- filter,
- removeOperation
- )
- })
- it('should send a group-subscription-left event for each group', async function () {
- this.fakeSub1 = {
- _id: 'fake-id-1',
- groupPlan: true,
- recurlySubscription_id: 'fake-sub-1',
- }
- this.fakeSub2 = {
- _id: 'fake-id-2',
- groupPlan: true,
- recurlySubscription_id: 'fake-sub-2',
- }
- this.SubscriptionModel.findOne
- .withArgs(
- { _id: 'fake-id-1' },
- { recurlySubscription_id: 1, groupPlan: 1 }
- )
- .resolves(this.fakeSub1)
- .withArgs(
- { _id: 'fake-id-2' },
- { recurlySubscription_id: 1, groupPlan: 1 }
- )
- .resolves(this.fakeSub2)
- await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.recordEventForUserInBackground,
- this.otherUserId,
- 'group-subscription-left',
- {
- groupId: 'fake-id-1',
- subscriptionId: 'fake-sub-1',
- }
- )
- sinon.assert.calledWith(
- this.AnalyticsManager.recordEventForUserInBackground,
- this.otherUserId,
- 'group-subscription-left',
- {
- groupId: 'fake-id-2',
- subscriptionId: 'fake-sub-2',
- }
- )
- })
- it('should add an audit log entry for each group the user leaves', async function () {
- await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
- this.otherUserId
- )
- sinon.assert.calledWith(
- this.UserAuditLogHandler.promises.addEntry,
- this.otherUserId,
- 'leave-group-subscription',
- undefined,
- undefined,
- {
- subscriptionId: 'fake-id-1',
- }
- )
- sinon.assert.calledWith(
- this.UserAuditLogHandler.promises.addEntry,
- this.otherUserId,
- 'leave-group-subscription',
- undefined,
- undefined,
- {
- subscriptionId: 'fake-id-2',
- }
- )
- })
- })
- describe('deleteSubscription', function () {
- beforeEach(async function () {
- this.subscription = {
- _id: new ObjectId().toString(),
- mock: 'subscription',
- admin_id: new ObjectId(),
- member_ids: [new ObjectId(), new ObjectId(), new ObjectId()],
- }
- await this.SubscriptionUpdater.promises.deleteSubscription(
- this.subscription,
- {}
- )
- })
- it('should remove the subscription', function () {
- this.SubscriptionModel.deleteOne
- .calledWith({ _id: this.subscription._id })
- .should.equal(true)
- })
- it('should downgrade the admin_id', function () {
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(this.subscription.admin_id)
- })
- it('should downgrade all of the members', function () {
- for (const userId of this.subscription.member_ids) {
- expect(
- this.FeaturesUpdater.promises.scheduleRefreshFeatures
- ).to.have.been.calledWith(userId)
- }
- })
- })
- })
|