mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Features ESM conversion
GitOrigin-RevId: d659326723a90ac0789f4f7acc7f00aa9eaa63e2
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
const { URL, URLSearchParams } = require('url')
|
||||
const OError = require('@overleaf/o-error')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const {
|
||||
import { URL, URLSearchParams } from 'node:url'
|
||||
import OError from '@overleaf/o-error'
|
||||
import Settings from '@overleaf/settings'
|
||||
import {
|
||||
fetchNothing,
|
||||
fetchStringWithResponse,
|
||||
RequestFailedError,
|
||||
} = require('@overleaf/fetch-utils')
|
||||
const RedisWrapper = require('../../infrastructure/RedisWrapper')
|
||||
const Cookie = require('cookie')
|
||||
const logger = require('@overleaf/logger')
|
||||
const Metrics = require('@overleaf/metrics')
|
||||
} from '@overleaf/fetch-utils'
|
||||
import RedisWrapper from '../../infrastructure/RedisWrapper.js'
|
||||
import Cookie from 'cookie'
|
||||
import logger from '@overleaf/logger'
|
||||
import Metrics from '@overleaf/metrics'
|
||||
|
||||
const clsiCookiesEnabled = (Settings.clsiCookie?.key ?? '') !== ''
|
||||
|
||||
@@ -256,4 +256,4 @@ const ClsiCookieManagerFactory = function (backendGroup) {
|
||||
return cookieManager
|
||||
}
|
||||
|
||||
module.exports = ClsiCookieManagerFactory
|
||||
export default ClsiCookieManagerFactory
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const _ = require('lodash')
|
||||
const settings = require('@overleaf/settings')
|
||||
import _ from 'lodash'
|
||||
import settings from '@overleaf/settings'
|
||||
|
||||
const ClsiFormatChecker = {
|
||||
checkRecoursesForProblems(resources) {
|
||||
@@ -56,4 +56,4 @@ const ClsiFormatChecker = {
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = ClsiFormatChecker
|
||||
export default ClsiFormatChecker
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/* eslint-disable
|
||||
n/handle-callback-err,
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
@@ -13,11 +8,8 @@
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let ClsiStateManager
|
||||
const Settings = require('@overleaf/settings')
|
||||
const logger = require('@overleaf/logger')
|
||||
const crypto = require('crypto')
|
||||
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
import crypto from 'node:crypto'
|
||||
import ProjectEntityHandler from '../Project/ProjectEntityHandler.js'
|
||||
|
||||
// The "state" of a project is a hash of the relevant attributes in the
|
||||
// project object in this case we only need the rootFolder.
|
||||
@@ -36,7 +28,7 @@ const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
const buildState = s =>
|
||||
crypto.createHash('sha1').update(s, 'utf8').digest('hex')
|
||||
|
||||
module.exports = ClsiStateManager = {
|
||||
export default {
|
||||
computeHash(project, options) {
|
||||
const { docs, files } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(project)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { promisify, callbackify } = require('util')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const UserMembershipsHandler = require('../UserMembership/UserMembershipsHandler')
|
||||
const UserMembershipEntityConfigs = require('../UserMembership/UserMembershipEntityConfigs')
|
||||
import { promisify, callbackify } from 'node:util'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import UserMembershipsHandler from '../UserMembership/UserMembershipsHandler.js'
|
||||
import UserMembershipEntityConfigs from '../UserMembership/UserMembershipEntityConfigs.js'
|
||||
|
||||
async function getCurrentAffiliations(userId) {
|
||||
const fullEmails = await UserGetter.promises.getUserFullEmails(userId)
|
||||
@@ -98,4 +98,4 @@ InstitutionsGetter.promises = {
|
||||
getManagedInstitutions: promisify(InstitutionsGetter.getManagedInstitutions),
|
||||
}
|
||||
|
||||
module.exports = InstitutionsGetter
|
||||
export default InstitutionsGetter
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
const {
|
||||
callbackifyAll,
|
||||
promiseMapWithLimit,
|
||||
} = require('@overleaf/promise-utils')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const logger = require('@overleaf/logger')
|
||||
const { fetchJson } = require('@overleaf/fetch-utils')
|
||||
const InstitutionsAPI = require('./InstitutionsAPI')
|
||||
const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
|
||||
const FeaturesHelper = require('../Subscription/FeaturesHelper')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
||||
const NotificationsHandler = require('../Notifications/NotificationsHandler')
|
||||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||
const { Institution } = require('../../models/Institution')
|
||||
const { Subscription } = require('../../models/Subscription')
|
||||
const OError = require('@overleaf/o-error')
|
||||
import { callbackifyAll, promiseMapWithLimit } from '@overleaf/promise-utils'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import Settings from '@overleaf/settings'
|
||||
import logger from '@overleaf/logger'
|
||||
import { fetchJson } from '@overleaf/fetch-utils'
|
||||
import InstitutionsAPI from './InstitutionsAPI.js'
|
||||
import FeaturesUpdater from '../Subscription/FeaturesUpdater.js'
|
||||
import FeaturesHelper from '../Subscription/FeaturesHelper.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import NotificationsBuilder from '../Notifications/NotificationsBuilder.js'
|
||||
import NotificationsHandler from '../Notifications/NotificationsHandler.js'
|
||||
import SubscriptionLocator from '../Subscription/SubscriptionLocator.js'
|
||||
import { Institution } from '../../models/Institution.js'
|
||||
import { Subscription } from '../../models/Subscription.js'
|
||||
import OError from '@overleaf/o-error'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const ASYNC_LIMIT = parseInt(process.env.ASYNC_LIMIT, 10) || 5
|
||||
|
||||
@@ -355,7 +354,7 @@ async function affiliateUserByReversedHostname(user, reversedHostname) {
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
...callbackifyAll(InstitutionsManager),
|
||||
promises: InstitutionsManager,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import EmailHandler from '../Email/EmailHandler.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import './SubscriptionEmailBuilder.js'
|
||||
import './SubscriptionEmailBuilder.mjs'
|
||||
import PlansLocator from './PlansLocator.js'
|
||||
import Settings from '@overleaf/settings'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import dateformat from 'dateformat';
|
||||
import dateformat from 'dateformat'
|
||||
|
||||
function formatDateTime(date) {
|
||||
if (!date) {
|
||||
@@ -17,4 +17,4 @@ function formatDate(date) {
|
||||
export default {
|
||||
formatDateTime,
|
||||
formatDate,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiCookieManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
import sinon from 'sinon'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiCookieManager.mjs'
|
||||
|
||||
describe('ClsiCookieManager', function () {
|
||||
beforeEach(function () {
|
||||
this.redis = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.redis = {
|
||||
auth() {},
|
||||
del: sinon.stub(),
|
||||
get: sinon.stub(),
|
||||
setex: sinon.stub().resolves(),
|
||||
}
|
||||
this.project_id = '123423431321-proj-id'
|
||||
this.user_id = 'abc-user-id'
|
||||
this.fetchUtils = {
|
||||
ctx.project_id = '123423431321-proj-id'
|
||||
ctx.user_id = 'abc-user-id'
|
||||
ctx.fetchUtils = {
|
||||
fetchNothing: sinon.stub().returns(Promise.resolve()),
|
||||
fetchStringWithResponse: sinon.stub().returns(Promise.resolve()),
|
||||
}
|
||||
this.metrics = { inc: sinon.stub() }
|
||||
this.settings = {
|
||||
ctx.metrics = { inc: sinon.stub() }
|
||||
ctx.settings = {
|
||||
redis: {
|
||||
web: 'redis.something',
|
||||
},
|
||||
@@ -33,314 +32,319 @@ describe('ClsiCookieManager', function () {
|
||||
key: 'coooookie',
|
||||
},
|
||||
}
|
||||
this.requires = {
|
||||
'../../infrastructure/RedisWrapper': (this.RedisWrapper = {
|
||||
client: () => this.redis,
|
||||
vi.doMock('../../../../app/src/infrastructure/RedisWrapper', () => ({
|
||||
default: (ctx.RedisWrapper = {
|
||||
client: () => ctx.redis,
|
||||
}),
|
||||
'@overleaf/settings': this.settings,
|
||||
'@overleaf/fetch-utils': this.fetchUtils,
|
||||
'@overleaf/metrics': this.metrics,
|
||||
}
|
||||
this.ClsiCookieManager = SandboxedModule.require(modulePath, {
|
||||
requires: this.requires,
|
||||
})()
|
||||
}))
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: ctx.settings,
|
||||
}))
|
||||
vi.doMock('@overleaf/fetch-utils', () => ctx.fetchUtils)
|
||||
vi.doMock('@overleaf/metrics', () => ({
|
||||
default: ctx.metrics,
|
||||
}))
|
||||
|
||||
ctx.ClsiCookieManager = (await import(modulePath)).default()
|
||||
})
|
||||
|
||||
describe('getServerId', function () {
|
||||
it('should call get for the key', async function () {
|
||||
this.redis.get.resolves('clsi-7')
|
||||
const serverId = await this.ClsiCookieManager.promises.getServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
it('should call get for the key', async function (ctx) {
|
||||
ctx.redis.get.resolves('clsi-7')
|
||||
const serverId = await ctx.ClsiCookieManager.promises.getServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
)
|
||||
this.redis.get
|
||||
.calledWith(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.should.equal(true)
|
||||
serverId.should.equal('clsi-7')
|
||||
})
|
||||
|
||||
it('should fallback to old key', async function () {
|
||||
this.redis.get
|
||||
.withArgs(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
|
||||
it('should fallback to old key', async function (ctx) {
|
||||
ctx.redis.get
|
||||
.withArgs(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.resolves(null)
|
||||
this.redis.get
|
||||
.withArgs(`clsiserver:${this.project_id}:${this.user_id}`)
|
||||
ctx.redis.get
|
||||
.withArgs(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
|
||||
.resolves('clsi-7')
|
||||
const serverId = await this.ClsiCookieManager.promises.getServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
const serverId = await ctx.ClsiCookieManager.promises.getServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
)
|
||||
this.redis.get
|
||||
.calledWith(`clsiserver:n2d:${this.project_id}:${this.user_id}`)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
|
||||
.should.equal(true)
|
||||
this.redis.get
|
||||
.calledWith(`clsiserver:${this.project_id}:${this.user_id}`)
|
||||
ctx.redis.get
|
||||
.calledWith(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
|
||||
.should.equal(true)
|
||||
serverId.should.equal('clsi-7')
|
||||
})
|
||||
|
||||
it('should _populateServerIdViaRequest if no key is found', async function () {
|
||||
this.ClsiCookieManager.promises._populateServerIdViaRequest = sinon
|
||||
it('should _populateServerIdViaRequest if no key is found', async function (ctx) {
|
||||
ctx.ClsiCookieManager.promises._populateServerIdViaRequest = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
this.redis.get.resolves(null)
|
||||
await this.ClsiCookieManager.promises.getServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
ctx.redis.get.resolves(null)
|
||||
await ctx.ClsiCookieManager.promises.getServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
''
|
||||
)
|
||||
this.ClsiCookieManager.promises._populateServerIdViaRequest
|
||||
.calledWith(this.project_id, this.user_id)
|
||||
ctx.ClsiCookieManager.promises._populateServerIdViaRequest
|
||||
.calledWith(ctx.project_id, ctx.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should _populateServerIdViaRequest if no key is blank', async function () {
|
||||
this.ClsiCookieManager.promises._populateServerIdViaRequest = sinon
|
||||
it('should _populateServerIdViaRequest if no key is blank', async function (ctx) {
|
||||
ctx.ClsiCookieManager.promises._populateServerIdViaRequest = sinon
|
||||
.stub()
|
||||
.resolves(null)
|
||||
this.redis.get.resolves('')
|
||||
await this.ClsiCookieManager.promises.getServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
ctx.redis.get.resolves('')
|
||||
await ctx.ClsiCookieManager.promises.getServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
)
|
||||
this.ClsiCookieManager.promises._populateServerIdViaRequest
|
||||
.calledWith(this.project_id, this.user_id)
|
||||
ctx.ClsiCookieManager.promises._populateServerIdViaRequest
|
||||
.calledWith(ctx.project_id, ctx.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_populateServerIdViaRequest', function () {
|
||||
beforeEach(function () {
|
||||
this.clsiServerId = 'server-id'
|
||||
this.ClsiCookieManager.promises.setServerId = sinon.stub().resolves()
|
||||
beforeEach(function (ctx) {
|
||||
ctx.clsiServerId = 'server-id'
|
||||
ctx.ClsiCookieManager.promises.setServerId = sinon.stub().resolves()
|
||||
})
|
||||
|
||||
describe('with a server id in the response', function () {
|
||||
beforeEach(function () {
|
||||
this.response = {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.response = {
|
||||
headers: {
|
||||
'set-cookie': [
|
||||
`${this.settings.clsiCookie.key}=${this.clsiServerId}`,
|
||||
`${ctx.settings.clsiCookie.key}=${ctx.clsiServerId}`,
|
||||
],
|
||||
},
|
||||
}
|
||||
this.fetchUtils.fetchNothing.returns(this.response)
|
||||
ctx.fetchUtils.fetchNothing.returns(ctx.response)
|
||||
})
|
||||
|
||||
it('should make a request to the clsi', async function () {
|
||||
await this.ClsiCookieManager.promises._populateServerIdViaRequest(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
it('should make a request to the clsi', async function (ctx) {
|
||||
await ctx.ClsiCookieManager.promises._populateServerIdViaRequest(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d'
|
||||
)
|
||||
const args = this.ClsiCookieManager.promises.setServerId.args[0]
|
||||
args[0].should.equal(this.project_id)
|
||||
args[1].should.equal(this.user_id)
|
||||
const args = ctx.ClsiCookieManager.promises.setServerId.args[0]
|
||||
args[0].should.equal(ctx.project_id)
|
||||
args[1].should.equal(ctx.user_id)
|
||||
args[2].should.equal('standard')
|
||||
args[3].should.equal('n2d')
|
||||
args[4].should.deep.equal(this.clsiServerId)
|
||||
args[4].should.deep.equal(ctx.clsiServerId)
|
||||
})
|
||||
|
||||
it('should return the server id', async function () {
|
||||
it('should return the server id', async function (ctx) {
|
||||
const serverId =
|
||||
await this.ClsiCookieManager.promises._populateServerIdViaRequest(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
await ctx.ClsiCookieManager.promises._populateServerIdViaRequest(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'',
|
||||
'n2d'
|
||||
)
|
||||
serverId.should.equal(this.clsiServerId)
|
||||
serverId.should.equal(ctx.clsiServerId)
|
||||
})
|
||||
})
|
||||
|
||||
describe('without a server id in the response', function () {
|
||||
beforeEach(function () {
|
||||
this.response = { headers: {} }
|
||||
this.fetchUtils.fetchNothing.returns(this.response)
|
||||
beforeEach(function (ctx) {
|
||||
ctx.response = { headers: {} }
|
||||
ctx.fetchUtils.fetchNothing.returns(ctx.response)
|
||||
})
|
||||
it('should not set the server id there is no server id in the response', async function () {
|
||||
this.ClsiCookieManager._parseServerIdFromResponse = sinon
|
||||
it('should not set the server id there is no server id in the response', async function (ctx) {
|
||||
ctx.ClsiCookieManager._parseServerIdFromResponse = sinon
|
||||
.stub()
|
||||
.returns(null)
|
||||
await this.ClsiCookieManager.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
await ctx.ClsiCookieManager.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
this.redis.setex.called.should.equal(false)
|
||||
ctx.redis.setex.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearServerId', function () {
|
||||
it('should clear both keys', async function () {
|
||||
await this.ClsiCookieManager.promises.clearServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
it('should clear both keys', async function (ctx) {
|
||||
await ctx.ClsiCookieManager.promises.clearServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'n2d'
|
||||
)
|
||||
this.redis.del.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
|
||||
`clsiserver:${this.project_id}:${this.user_id}`
|
||||
ctx.redis.del.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
`clsiserver:${ctx.project_id}:${ctx.user_id}`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setServerId', function () {
|
||||
beforeEach(function () {
|
||||
this.clsiServerId = 'server-id'
|
||||
this.ClsiCookieManager._parseServerIdFromResponse = sinon
|
||||
beforeEach(function (ctx) {
|
||||
ctx.clsiServerId = 'server-id'
|
||||
ctx.ClsiCookieManager._parseServerIdFromResponse = sinon
|
||||
.stub()
|
||||
.returns('clsi-8')
|
||||
})
|
||||
|
||||
it('should set the server id with a ttl', async function () {
|
||||
await this.ClsiCookieManager.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
it('should set the server id with a ttl', async function (ctx) {
|
||||
await ctx.ClsiCookieManager.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
this.redis.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
|
||||
this.settings.clsiCookie.ttlInSeconds,
|
||||
this.clsiServerId
|
||||
ctx.redis.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSeconds,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
})
|
||||
|
||||
it('should set the server id with the regular ttl for reg instance', async function () {
|
||||
this.clsiServerId = 'clsi-reg-8'
|
||||
await this.ClsiCookieManager.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
it('should set the server id with the regular ttl for reg instance', async function (ctx) {
|
||||
ctx.clsiServerId = 'clsi-reg-8'
|
||||
await ctx.ClsiCookieManager.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
expect(this.redis.setex).to.have.been.calledWith(
|
||||
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
|
||||
this.settings.clsiCookie.ttlInSecondsRegular,
|
||||
this.clsiServerId
|
||||
expect(ctx.redis.setex).to.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSecondsRegular,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
})
|
||||
|
||||
it('should not set the server id if clsiCookies are not enabled', async function () {
|
||||
delete this.settings.clsiCookie.key
|
||||
this.ClsiCookieManager2 = SandboxedModule.require(modulePath, {
|
||||
globals: {
|
||||
console,
|
||||
},
|
||||
requires: this.requires,
|
||||
})()
|
||||
await this.ClsiCookieManager2.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
null
|
||||
)
|
||||
this.redis.setex.called.should.equal(false)
|
||||
describe('when clsiCookies are not enabled', function (ctx) {
|
||||
let oldKey
|
||||
beforeEach(async function (ctx) {
|
||||
oldKey = ctx.settings.clsiCookie.key
|
||||
delete ctx.settings.clsiCookie.key
|
||||
vi.resetModules()
|
||||
ctx.ClsiCookieManager2 = (await import(modulePath)).default()
|
||||
})
|
||||
afterEach(function (ctx) {
|
||||
ctx.settings.clsiCookie.key = oldKey
|
||||
})
|
||||
|
||||
it('should not set the server id if clsiCookies are not enabled', async function (ctx) {
|
||||
await ctx.ClsiCookieManager2.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
ctx.redis.setex.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('should also set in the secondary if secondary redis is enabled', async function () {
|
||||
this.redis_secondary = { setex: sinon.stub().resolves() }
|
||||
this.settings.redis.clsi_cookie_secondary = {}
|
||||
this.RedisWrapper.client = sinon.stub()
|
||||
this.RedisWrapper.client.withArgs('clsi_cookie').returns(this.redis)
|
||||
this.RedisWrapper.client
|
||||
it('should also set in the secondary if secondary redis is enabled', async function (ctx) {
|
||||
ctx.redis_secondary = { setex: sinon.stub().resolves() }
|
||||
ctx.settings.redis.clsi_cookie_secondary = {}
|
||||
ctx.RedisWrapper.client = sinon.stub()
|
||||
ctx.RedisWrapper.client.withArgs('clsi_cookie').returns(ctx.redis)
|
||||
ctx.RedisWrapper.client
|
||||
.withArgs('clsi_cookie_secondary')
|
||||
.returns(this.redis_secondary)
|
||||
this.ClsiCookieManager2 = SandboxedModule.require(modulePath, {
|
||||
globals: {
|
||||
console,
|
||||
},
|
||||
requires: this.requires,
|
||||
})()
|
||||
this.ClsiCookieManager2._parseServerIdFromResponse = sinon
|
||||
.returns(ctx.redis_secondary)
|
||||
vi.resetModules()
|
||||
ctx.ClsiCookieManager2 = (await import(modulePath)).default()
|
||||
ctx.ClsiCookieManager2._parseServerIdFromResponse = sinon
|
||||
.stub()
|
||||
.returns('clsi-8')
|
||||
await this.ClsiCookieManager2.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
await ctx.ClsiCookieManager2.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
ctx.clsiServerId,
|
||||
null
|
||||
)
|
||||
this.redis_secondary.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${this.project_id}:${this.user_id}`,
|
||||
this.settings.clsiCookie.ttlInSeconds,
|
||||
this.clsiServerId
|
||||
ctx.redis_secondary.setex.should.have.been.calledWith(
|
||||
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
|
||||
ctx.settings.clsiCookie.ttlInSeconds,
|
||||
ctx.clsiServerId
|
||||
)
|
||||
})
|
||||
|
||||
describe('checkIsLoadSheddingEvent', function () {
|
||||
beforeEach(function () {
|
||||
this.fetchUtils.fetchStringWithResponse.reset()
|
||||
this.call = async () => {
|
||||
await this.ClsiCookieManager.promises.setServerId(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fetchUtils.fetchStringWithResponse.reset()
|
||||
ctx.call = async () => {
|
||||
await ctx.ClsiCookieManager.promises.setServerId(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
'standard',
|
||||
'n2d',
|
||||
this.clsiServerId,
|
||||
ctx.clsiServerId,
|
||||
'previous-clsi-server-id'
|
||||
)
|
||||
expect(
|
||||
this.fetchUtils.fetchStringWithResponse
|
||||
ctx.fetchUtils.fetchStringWithResponse
|
||||
).to.have.been.calledWith(
|
||||
`${this.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=n2d`,
|
||||
`${ctx.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=n2d`,
|
||||
{ method: 'GET', signal: sinon.match.instanceOf(AbortSignal) }
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should report "load-shedding" when previous is UP', async function () {
|
||||
this.fetchUtils.fetchStringWithResponse.resolves({
|
||||
it('should report "load-shedding" when previous is UP', async function (ctx) {
|
||||
ctx.fetchUtils.fetchStringWithResponse.resolves({
|
||||
response: { status: 200 },
|
||||
body: 'previous-clsi-server-id,UP\n',
|
||||
})
|
||||
await this.call()
|
||||
expect(this.metrics.inc).to.have.been.calledWith(
|
||||
await ctx.call()
|
||||
expect(ctx.metrics.inc).to.have.been.calledWith(
|
||||
'clsi-lb-switch-backend',
|
||||
1,
|
||||
{ status: 'load-shedding' }
|
||||
)
|
||||
})
|
||||
|
||||
it('should report "cycle" when other is UP', async function () {
|
||||
this.fetchUtils.fetchStringWithResponse.resolves({
|
||||
it('should report "cycle" when other is UP', async function (ctx) {
|
||||
ctx.fetchUtils.fetchStringWithResponse.resolves({
|
||||
response: { status: 200 },
|
||||
body: 'other-clsi-server-id,UP\n',
|
||||
})
|
||||
await this.call()
|
||||
expect(this.metrics.inc).to.have.been.calledWith(
|
||||
await ctx.call()
|
||||
expect(ctx.metrics.inc).to.have.been.calledWith(
|
||||
'clsi-lb-switch-backend',
|
||||
1,
|
||||
{ status: 'cycle' }
|
||||
)
|
||||
})
|
||||
|
||||
it('should report "cycle" when previous is 404', async function () {
|
||||
this.fetchUtils.fetchStringWithResponse.resolves({
|
||||
it('should report "cycle" when previous is 404', async function (ctx) {
|
||||
ctx.fetchUtils.fetchStringWithResponse.resolves({
|
||||
response: { status: 404 },
|
||||
})
|
||||
await this.call()
|
||||
expect(this.metrics.inc).to.have.been.calledWith(
|
||||
await ctx.call()
|
||||
expect(ctx.metrics.inc).to.have.been.calledWith(
|
||||
'clsi-lb-switch-backend',
|
||||
1,
|
||||
{ status: 'cycle' }
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiFormatChecker.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiFormatChecker.mjs'
|
||||
|
||||
describe('ClsiFormatChecker', function () {
|
||||
beforeEach(function () {
|
||||
this.ClsiFormatChecker = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'@overleaf/settings': (this.settings = {
|
||||
compileBodySizeLimitMb: 5,
|
||||
}),
|
||||
},
|
||||
})
|
||||
return (this.project_id = 'project-id')
|
||||
beforeEach(async function (ctx) {
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {
|
||||
compileBodySizeLimitMb: 5,
|
||||
}),
|
||||
}))
|
||||
|
||||
ctx.ClsiFormatChecker = (await import(modulePath)).default
|
||||
ctx.project_id = 'project-id'
|
||||
})
|
||||
|
||||
describe('checkRecoursesForProblems', function () {
|
||||
beforeEach(function () {
|
||||
return (this.resources = [
|
||||
beforeEach(function (ctx) {
|
||||
ctx.resources = [
|
||||
{
|
||||
path: 'main.tex',
|
||||
content: 'stuff',
|
||||
@@ -28,65 +28,59 @@ describe('ClsiFormatChecker', function () {
|
||||
},
|
||||
{
|
||||
path: 'stuff/image/image.png',
|
||||
url: `http:somewhere.com/project/${this.project_id}/file/1234124321312`,
|
||||
url: `http:somewhere.com/project/${ctx.project_id}/file/1234124321312`,
|
||||
modified: 'more stuff',
|
||||
},
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', async function () {
|
||||
this.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
it('should call _checkDocsAreUnderSizeLimit and _checkForConflictingPaths', async function (ctx) {
|
||||
ctx.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
.stub()
|
||||
.returns(null)
|
||||
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
.stub()
|
||||
.returns(null)
|
||||
this.ClsiFormatChecker.checkRecoursesForProblems(this.resources)
|
||||
this.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(true)
|
||||
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
|
||||
await ctx.ClsiFormatChecker.checkRecoursesForProblems(ctx.resources)
|
||||
ctx.ClsiFormatChecker._checkForConflictingPaths.called.should.equal(true)
|
||||
ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit.called.should.equal(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove undefined errors', async function () {
|
||||
this.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
.stub()
|
||||
.returns([])
|
||||
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
it('should remove undefined errors', async function (ctx) {
|
||||
ctx.ClsiFormatChecker._checkForConflictingPaths = sinon.stub().returns([])
|
||||
ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
.stub()
|
||||
.returns({})
|
||||
const problems = this.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
this.resources
|
||||
const problems = await ctx.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
ctx.resources
|
||||
)
|
||||
expect(problems).to.not.exist
|
||||
})
|
||||
|
||||
it('should keep populated arrays', async function () {
|
||||
this.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
it('should keep populated arrays', async function (ctx) {
|
||||
ctx.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
.stub()
|
||||
.returns([{ path: 'somewhere/main.tex' }])
|
||||
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
.stub()
|
||||
.returns({})
|
||||
const problems = this.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
this.resources
|
||||
const problems = await ctx.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
ctx.resources
|
||||
)
|
||||
problems.conflictedPaths[0].path.should.equal('somewhere/main.tex')
|
||||
expect(problems.sizeCheck).to.not.exist
|
||||
})
|
||||
|
||||
it('should keep populated object', async function () {
|
||||
this.ClsiFormatChecker._checkForConflictingPaths = sinon
|
||||
.stub()
|
||||
.returns([])
|
||||
this.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon
|
||||
.stub()
|
||||
.returns({
|
||||
resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
|
||||
totalSize: 1000000,
|
||||
})
|
||||
const problems = this.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
this.resources
|
||||
it('should keep populated object', async function (ctx) {
|
||||
ctx.ClsiFormatChecker._checkForConflictingPaths = sinon.stub().returns([])
|
||||
ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit = sinon.stub().returns({
|
||||
resources: [{ 'a.tex': 'a.tex' }, { 'b.tex': 'b.tex' }],
|
||||
totalSize: 1000000,
|
||||
})
|
||||
const problems = await ctx.ClsiFormatChecker.checkRecoursesForProblems(
|
||||
ctx.resources
|
||||
)
|
||||
problems.sizeCheck.resources.length.should.equal(2)
|
||||
problems.sizeCheck.totalSize.should.equal(1000000)
|
||||
@@ -94,93 +88,91 @@ describe('ClsiFormatChecker', function () {
|
||||
})
|
||||
|
||||
describe('_checkForConflictingPaths', function () {
|
||||
beforeEach(function () {
|
||||
this.resources.push({
|
||||
beforeEach(function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'chapters/chapter1.tex',
|
||||
content: 'other stuff',
|
||||
})
|
||||
|
||||
return this.resources.push({
|
||||
ctx.resources.push({
|
||||
path: 'chapters.tex',
|
||||
content: 'other stuff',
|
||||
})
|
||||
})
|
||||
|
||||
it('should flag up when a nested file has folder with same subpath as file elsewhere', async function () {
|
||||
this.resources.push({
|
||||
it('should flag up when a nested file has folder with same subpath as file elsewhere', async function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'stuff/image',
|
||||
url: 'http://somwhere.com',
|
||||
})
|
||||
|
||||
const conflictPathErrors =
|
||||
this.ClsiFormatChecker._checkForConflictingPaths(this.resources)
|
||||
await ctx.ClsiFormatChecker._checkForConflictingPaths(ctx.resources)
|
||||
conflictPathErrors.length.should.equal(1)
|
||||
conflictPathErrors[0].path.should.equal('stuff/image')
|
||||
})
|
||||
|
||||
it('should flag up when a root level file has folder with same subpath as file elsewhere', async function () {
|
||||
this.resources.push({
|
||||
it('should flag up when a root level file has folder with same subpath as file elsewhere', async function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'stuff',
|
||||
content: 'other stuff',
|
||||
})
|
||||
|
||||
const conflictPathErrors =
|
||||
this.ClsiFormatChecker._checkForConflictingPaths(this.resources)
|
||||
await ctx.ClsiFormatChecker._checkForConflictingPaths(ctx.resources)
|
||||
conflictPathErrors.length.should.equal(1)
|
||||
conflictPathErrors[0].path.should.equal('stuff')
|
||||
})
|
||||
|
||||
it('should not flag up when the file is a substring of a path', async function () {
|
||||
this.resources.push({
|
||||
it('should not flag up when the file is a substring of a path', async function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'stuf',
|
||||
content: 'other stuff',
|
||||
})
|
||||
|
||||
const conflictPathErrors =
|
||||
this.ClsiFormatChecker._checkForConflictingPaths(this.resources)
|
||||
await ctx.ClsiFormatChecker._checkForConflictingPaths(ctx.resources)
|
||||
conflictPathErrors.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkDocsAreUnderSizeLimit', function () {
|
||||
it('should error when there is more than 5mb of data', async function () {
|
||||
this.resources.push({
|
||||
it('should error when there is more than 5mb of data', async function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'massive.tex',
|
||||
content: 'hello world'.repeat(833333), // over 5mb limit
|
||||
})
|
||||
|
||||
while (this.resources.length < 20) {
|
||||
this.resources.push({
|
||||
while (ctx.resources.length < 20) {
|
||||
ctx.resources.push({
|
||||
path: 'chapters/chapter1.tex',
|
||||
url: 'http://somwhere.com',
|
||||
})
|
||||
}
|
||||
|
||||
const sizeError = this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
|
||||
this.resources
|
||||
)
|
||||
const sizeError =
|
||||
await ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit(ctx.resources)
|
||||
sizeError.totalSize.should.equal(16 + 833333 * 11) // 16 is for earlier resources
|
||||
sizeError.resources.length.should.equal(10)
|
||||
sizeError.resources[0].path.should.equal('massive.tex')
|
||||
sizeError.resources[0].size.should.equal(833333 * 11)
|
||||
})
|
||||
|
||||
it('should return nothing when project is correct size', async function () {
|
||||
this.resources.push({
|
||||
it('should return nothing when project is correct size', async function (ctx) {
|
||||
ctx.resources.push({
|
||||
path: 'massive.tex',
|
||||
content: 'x'.repeat(2 * 1000 * 1000),
|
||||
})
|
||||
|
||||
while (this.resources.length < 20) {
|
||||
this.resources.push({
|
||||
while (ctx.resources.length < 20) {
|
||||
ctx.resources.push({
|
||||
path: 'chapters/chapter1.tex',
|
||||
url: 'http://somwhere.com',
|
||||
})
|
||||
}
|
||||
|
||||
const sizeError = this.ClsiFormatChecker._checkDocsAreUnderSizeLimit(
|
||||
this.resources
|
||||
)
|
||||
const sizeError =
|
||||
await ctx.ClsiFormatChecker._checkDocsAreUnderSizeLimit(ctx.resources)
|
||||
expect(sizeError).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,42 +1,34 @@
|
||||
/* eslint-disable
|
||||
n/handle-callback-err,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiStateManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/Compile/ClsiStateManager.mjs'
|
||||
|
||||
describe('ClsiStateManager', function () {
|
||||
beforeEach(function () {
|
||||
this.ClsiStateManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'@overleaf/settings': (this.settings = {}),
|
||||
'../Project/ProjectEntityHandler': (this.ProjectEntityHandler = {}),
|
||||
},
|
||||
})
|
||||
this.project = 'project'
|
||||
this.options = { draft: true, isAutoCompile: false }
|
||||
return (this.callback = sinon.stub())
|
||||
beforeEach(async function (ctx) {
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {}),
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityHandler',
|
||||
() => ({
|
||||
default: (ctx.ProjectEntityHandler = {}),
|
||||
})
|
||||
)
|
||||
|
||||
ctx.ClsiStateManager = (await import(modulePath)).default
|
||||
ctx.project = 'project'
|
||||
ctx.options = { draft: true, isAutoCompile: false }
|
||||
ctx.callback = sinon.stub()
|
||||
})
|
||||
|
||||
describe('computeHash', function () {
|
||||
beforeEach(function () {
|
||||
this.docs = [
|
||||
beforeEach(function (ctx) {
|
||||
ctx.docs = [
|
||||
{ path: '/main.tex', doc: { _id: 'doc-id-1' } },
|
||||
{ path: '/folder/sub.tex', doc: { _id: 'doc-id-2' } },
|
||||
]
|
||||
this.files = [
|
||||
ctx.files = [
|
||||
{
|
||||
path: '/figure.pdf',
|
||||
file: { _id: 'file-id-1', rev: 123, created: 'aaaaaa' },
|
||||
@@ -46,158 +38,155 @@ describe('ClsiStateManager', function () {
|
||||
file: { _id: 'file-id-2', rev: 456, created: 'bbbbbb' },
|
||||
},
|
||||
]
|
||||
this.ProjectEntityHandler.getAllEntitiesFromProject = sinon
|
||||
ctx.ProjectEntityHandler.getAllEntitiesFromProject = sinon
|
||||
.stub()
|
||||
.returns({ docs: this.docs, files: this.files })
|
||||
this.hash0 = this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
.returns({ docs: ctx.docs, files: ctx.files })
|
||||
ctx.hash0 = ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
})
|
||||
|
||||
describe('with a sample project', function () {
|
||||
beforeEach(function () {})
|
||||
|
||||
it('should return a hash value', function () {
|
||||
it('should return a hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).to.equal('21b1ab73aa3892bec452baf8ffa0956179e1880f')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the files and docs are in a different order', function () {
|
||||
beforeEach(function () {
|
||||
;[this.docs[0], this.docs[1]] = Array.from([this.docs[1], this.docs[0]])
|
||||
;[this.files[0], this.files[1]] = Array.from([
|
||||
this.files[1],
|
||||
this.files[0],
|
||||
])
|
||||
beforeEach(function (ctx) {
|
||||
;[ctx.docs[0], ctx.docs[1]] = [ctx.docs[1], ctx.docs[0]]
|
||||
;[ctx.files[0], ctx.files[1]] = [ctx.files[1], ctx.files[0]]
|
||||
})
|
||||
|
||||
it('should return the same hash value', function () {
|
||||
it('should return the same hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a doc is renamed', function () {
|
||||
beforeEach(function () {
|
||||
this.docs[0].path = '/new.tex'
|
||||
beforeEach(function (ctx) {
|
||||
ctx.docs[0].path = '/new.tex'
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a file is renamed', function () {
|
||||
beforeEach(function () {
|
||||
this.files[0].path = '/newfigure.pdf'
|
||||
beforeEach(function (ctx) {
|
||||
ctx.files[0].path = '/newfigure.pdf'
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a doc is added', function () {
|
||||
beforeEach(function () {
|
||||
this.docs.push({ path: '/newdoc.tex', doc: { _id: 'newdoc-id' } })
|
||||
beforeEach(function (ctx) {
|
||||
ctx.docs.push({ path: '/newdoc.tex', doc: { _id: 'newdoc-id' } })
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a file is added', function () {
|
||||
beforeEach(function () {
|
||||
this.files.push({
|
||||
beforeEach(function (ctx) {
|
||||
ctx.files.push({
|
||||
path: '/newfile.tex',
|
||||
file: { _id: 'newfile-id', rev: 123 },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a doc is removed', function () {
|
||||
beforeEach(function () {
|
||||
this.docs.pop()
|
||||
beforeEach(function (ctx) {
|
||||
ctx.docs.pop()
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a file is removed', function () {
|
||||
beforeEach(function () {
|
||||
this.files.pop()
|
||||
beforeEach(function (ctx) {
|
||||
ctx.files.pop()
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when a file's revision is updated", function () {
|
||||
beforeEach(function () {
|
||||
this.files[0].file.rev++
|
||||
beforeEach(function (ctx) {
|
||||
ctx.files[0].file.rev++
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when a file's date is updated", function () {
|
||||
beforeEach(function () {
|
||||
this.files[0].file.created = 'zzzzzz'
|
||||
beforeEach(function (ctx) {
|
||||
ctx.files[0].file.created = 'zzzzzz'
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the compile options are changed', function () {
|
||||
beforeEach(function () {
|
||||
this.options.draft = !this.options.draft
|
||||
beforeEach(function (ctx) {
|
||||
ctx.options.draft = !ctx.options.draft
|
||||
})
|
||||
|
||||
it('should return a different hash value', function () {
|
||||
it('should return a different hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).not.to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).not.to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the isAutoCompile option is changed', function () {
|
||||
beforeEach(function () {
|
||||
this.options.isAutoCompile = !this.options.isAutoCompile
|
||||
beforeEach(function (ctx) {
|
||||
ctx.options.isAutoCompile = !ctx.options.isAutoCompile
|
||||
})
|
||||
|
||||
it('should return the same hash value', function () {
|
||||
it('should return the same hash value', function (ctx) {
|
||||
expect(
|
||||
this.ClsiStateManager.computeHash(this.project, this.options)
|
||||
).to.equal(this.hash0)
|
||||
ctx.ClsiStateManager.computeHash(ctx.project, ctx.options)
|
||||
).to.equal(ctx.hash0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/Institutions/InstitutionsGetter.js'
|
||||
)
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Institutions/InstitutionsGetter.mjs'
|
||||
|
||||
describe('InstitutionsGetter', function () {
|
||||
beforeEach(function () {
|
||||
this.UserGetter = {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.UserGetter = {
|
||||
getUserFullEmails: sinon.stub(),
|
||||
promises: {
|
||||
getUserFullEmails: sinon.stub(),
|
||||
},
|
||||
}
|
||||
this.InstitutionsGetter = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'../UserMembership/UserMembershipsHandler':
|
||||
(this.UserMembershipsHandler = {}),
|
||||
'../UserMembership/UserMembershipEntityConfigs':
|
||||
(this.UserMembershipEntityConfigs = {}),
|
||||
},
|
||||
})
|
||||
|
||||
this.userId = '12345abcde'
|
||||
this.confirmedAffiliation = {
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/UserMembership/UserMembershipsHandler',
|
||||
() => ({
|
||||
default: (ctx.UserMembershipsHandler = {}),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs',
|
||||
() => ({
|
||||
default: (ctx.UserMembershipEntityConfigs = {}),
|
||||
})
|
||||
)
|
||||
|
||||
ctx.InstitutionsGetter = (await import(modulePath)).default
|
||||
|
||||
ctx.userId = '12345abcde'
|
||||
ctx.confirmedAffiliation = {
|
||||
confirmedAt: new Date(),
|
||||
affiliation: {
|
||||
institution: { id: 456, confirmed: true },
|
||||
@@ -33,7 +41,7 @@ describe('InstitutionsGetter', function () {
|
||||
pastReconfirmDate: false,
|
||||
},
|
||||
}
|
||||
this.confirmedAffiliationPastReconfirmation = {
|
||||
ctx.confirmedAffiliationPastReconfirmation = {
|
||||
confirmedAt: new Date('2000-01-01'),
|
||||
affiliation: {
|
||||
institution: { id: 135, confirmed: true },
|
||||
@@ -41,7 +49,7 @@ describe('InstitutionsGetter', function () {
|
||||
pastReconfirmDate: true,
|
||||
},
|
||||
}
|
||||
this.licencedAffiliation = {
|
||||
ctx.licencedAffiliation = {
|
||||
confirmedAt: new Date(),
|
||||
affiliation: {
|
||||
licence: 'pro_plus',
|
||||
@@ -50,7 +58,7 @@ describe('InstitutionsGetter', function () {
|
||||
pastReconfirmDate: false,
|
||||
},
|
||||
}
|
||||
this.licencedAffiliationPastReconfirmation = {
|
||||
ctx.licencedAffiliationPastReconfirmation = {
|
||||
confirmedAt: new Date('2000-01-01'),
|
||||
affiliation: {
|
||||
licence: 'pro_plus',
|
||||
@@ -59,7 +67,7 @@ describe('InstitutionsGetter', function () {
|
||||
pastReconfirmDate: true,
|
||||
},
|
||||
}
|
||||
this.unconfirmedEmailLicensedAffiliation = {
|
||||
ctx.unconfirmedEmailLicensedAffiliation = {
|
||||
confirmedAt: null,
|
||||
affiliation: {
|
||||
licence: 'pro_plus',
|
||||
@@ -71,7 +79,7 @@ describe('InstitutionsGetter', function () {
|
||||
},
|
||||
},
|
||||
}
|
||||
this.unconfirmedDomainLicensedAffiliation = {
|
||||
ctx.unconfirmedDomainLicensedAffiliation = {
|
||||
confirmedAt: new Date(),
|
||||
affiliation: {
|
||||
licence: 'pro_plus',
|
||||
@@ -83,7 +91,7 @@ describe('InstitutionsGetter', function () {
|
||||
},
|
||||
},
|
||||
}
|
||||
this.userEmails = [
|
||||
ctx.userEmails = [
|
||||
{
|
||||
confirmedAt: null,
|
||||
affiliation: {
|
||||
@@ -95,9 +103,9 @@ describe('InstitutionsGetter', function () {
|
||||
},
|
||||
},
|
||||
},
|
||||
this.confirmedAffiliation,
|
||||
this.confirmedAffiliation,
|
||||
this.confirmedAffiliationPastReconfirmation,
|
||||
ctx.confirmedAffiliation,
|
||||
ctx.confirmedAffiliation,
|
||||
ctx.confirmedAffiliationPastReconfirmation,
|
||||
{
|
||||
confirmedAt: new Date(),
|
||||
affiliation: null,
|
||||
@@ -124,41 +132,41 @@ describe('InstitutionsGetter', function () {
|
||||
},
|
||||
},
|
||||
]
|
||||
this.fullEmailCollection = [
|
||||
this.licencedAffiliation,
|
||||
this.licencedAffiliation,
|
||||
this.licencedAffiliationPastReconfirmation,
|
||||
this.confirmedAffiliation,
|
||||
this.confirmedAffiliationPastReconfirmation,
|
||||
this.unconfirmedDomainLicensedAffiliation,
|
||||
this.unconfirmedEmailLicensedAffiliation,
|
||||
ctx.fullEmailCollection = [
|
||||
ctx.licencedAffiliation,
|
||||
ctx.licencedAffiliation,
|
||||
ctx.licencedAffiliationPastReconfirmation,
|
||||
ctx.confirmedAffiliation,
|
||||
ctx.confirmedAffiliationPastReconfirmation,
|
||||
ctx.unconfirmedDomainLicensedAffiliation,
|
||||
ctx.unconfirmedEmailLicensedAffiliation,
|
||||
]
|
||||
})
|
||||
|
||||
describe('getCurrentInstitutionIds', function () {
|
||||
it('filters unconfirmed affiliations, those past reconfirmation, and returns only 1 result per institution', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.resolves(this.userEmails)
|
||||
it('filters unconfirmed affiliations, those past reconfirmation, and returns only 1 result per institution', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves(ctx.userEmails)
|
||||
const institutions =
|
||||
await this.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
ctx.userId
|
||||
)
|
||||
expect(institutions.length).to.equal(1)
|
||||
expect(institutions[0]).to.equal(456)
|
||||
})
|
||||
it('handles empty response', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.resolves([])
|
||||
it('handles empty response', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([])
|
||||
const institutions =
|
||||
await this.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
ctx.userId
|
||||
)
|
||||
expect(institutions).to.deep.equal([])
|
||||
})
|
||||
it('handles errors', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.rejects(new Error('oops'))
|
||||
it('handles errors', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.rejects(new Error('oops'))
|
||||
let e
|
||||
try {
|
||||
await this.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
ctx.userId
|
||||
)
|
||||
} catch (error) {
|
||||
e = error
|
||||
@@ -168,37 +176,37 @@ describe('InstitutionsGetter', function () {
|
||||
})
|
||||
|
||||
describe('getCurrentAndPastAffiliationIds', function () {
|
||||
it('filters unconfirmed affiliations, preserves those past reconfirmation, and returns only 1 result per institution', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.resolves(
|
||||
this.fullEmailCollection
|
||||
it('filters unconfirmed affiliations, preserves those past reconfirmation, and returns only 1 result per institution', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves(
|
||||
ctx.fullEmailCollection
|
||||
)
|
||||
const institutions =
|
||||
await this.InstitutionsGetter.promises.getCurrentAndPastAffiliationIds(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentAndPastAffiliationIds(
|
||||
ctx.userId
|
||||
)
|
||||
expect(institutions).to.deep.equal([777, 888, 456, 135])
|
||||
})
|
||||
it('handles empty response', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.resolves([])
|
||||
it('handles empty response', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([])
|
||||
const institutions =
|
||||
await this.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentInstitutionIds(
|
||||
ctx.userId
|
||||
)
|
||||
expect(institutions).to.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCurrentInstitutionsWithLicence', function () {
|
||||
it('returns one result per institution and filters out affiliations without license', async function () {
|
||||
this.UserGetter.promises.getUserFullEmails.resolves(
|
||||
this.fullEmailCollection
|
||||
it('returns one result per institution and filters out affiliations without license', async function (ctx) {
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves(
|
||||
ctx.fullEmailCollection
|
||||
)
|
||||
const institutions =
|
||||
await this.InstitutionsGetter.promises.getCurrentInstitutionsWithLicence(
|
||||
this.userId
|
||||
await ctx.InstitutionsGetter.promises.getCurrentInstitutionsWithLicence(
|
||||
ctx.userId
|
||||
)
|
||||
expect(institutions.map(institution => institution.id)).to.deep.equal([
|
||||
this.licencedAffiliation.affiliation.institution.id,
|
||||
ctx.licencedAffiliation.affiliation.institution.id,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,270 +1,311 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
import { vi, expect } from 'vitest'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import Features from '../../../../app/src/infrastructure/Features.js'
|
||||
const modulePath = path.join(
|
||||
__dirname,
|
||||
import.meta.dirname,
|
||||
'../../../../app/src/Features/Institutions/InstitutionsManager'
|
||||
)
|
||||
const Features = require('../../../../app/src/infrastructure/Features')
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
describe('InstitutionsManager', function () {
|
||||
beforeEach(function () {
|
||||
this.institutionId = 123
|
||||
this.user = {}
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.institutionId = 123
|
||||
ctx.user = {}
|
||||
const lapsedUser = {
|
||||
_id: '657300a08a14461b3d1aac3e',
|
||||
features: {},
|
||||
}
|
||||
this.users = [
|
||||
ctx.users = [
|
||||
lapsedUser,
|
||||
{ _id: '657300a08a14461b3d1aac3f', features: {} },
|
||||
{ _id: '657300a08a14461b3d1aac40', features: {} },
|
||||
{ _id: '657300a08a14461b3d1aac41', features: {} },
|
||||
]
|
||||
this.ssoUsers = [
|
||||
ctx.ssoUsers = [
|
||||
{
|
||||
_id: '657300a08a14461b3d1aac3f',
|
||||
samlIdentifiers: [{ providerId: this.institutionId.toString() }],
|
||||
samlIdentifiers: [{ providerId: ctx.institutionId.toString() }],
|
||||
},
|
||||
{
|
||||
_id: '657300a08a14461b3d1aac40',
|
||||
samlIdentifiers: [
|
||||
{
|
||||
providerId: this.institutionId.toString(),
|
||||
providerId: ctx.institutionId.toString(),
|
||||
hasEntitlement: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
_id: '657300a08a14461b3d1aac3e',
|
||||
samlIdentifiers: [{ providerId: this.institutionId.toString() }],
|
||||
samlIdentifiers: [{ providerId: ctx.institutionId.toString() }],
|
||||
hasEntitlement: true,
|
||||
},
|
||||
]
|
||||
|
||||
this.UserGetter = {
|
||||
ctx.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub().resolves(this.user),
|
||||
getUsers: sinon.stub().resolves(this.users),
|
||||
getUser: sinon.stub().resolves(ctx.user),
|
||||
getUsers: sinon.stub().resolves(ctx.users),
|
||||
getUsersByAnyConfirmedEmail: sinon.stub().resolves(),
|
||||
getSsoUsersAtInstitution: (this.getSsoUsersAtInstitution = sinon
|
||||
getSsoUsersAtInstitution: (ctx.getSsoUsersAtInstitution = sinon
|
||||
.stub()
|
||||
.resolves(this.ssoUsers)),
|
||||
.resolves(ctx.ssoUsers)),
|
||||
},
|
||||
}
|
||||
this.creator = { create: sinon.stub().resolves() }
|
||||
this.NotificationsBuilder = {
|
||||
ctx.creator = { create: sinon.stub().resolves() }
|
||||
ctx.NotificationsBuilder = {
|
||||
promises: {
|
||||
featuresUpgradedByAffiliation: sinon.stub().returns(this.creator),
|
||||
redundantPersonalSubscription: sinon.stub().returns(this.creator),
|
||||
featuresUpgradedByAffiliation: sinon.stub().returns(ctx.creator),
|
||||
redundantPersonalSubscription: sinon.stub().returns(ctx.creator),
|
||||
},
|
||||
}
|
||||
this.SubscriptionLocator = {
|
||||
ctx.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUsersSubscription: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.institutionWithV1Data = { name: 'Wombat University' }
|
||||
this.institution = {
|
||||
fetchV1DataPromise: sinon.stub().resolves(this.institutionWithV1Data),
|
||||
ctx.institutionWithV1Data = { name: 'Wombat University' }
|
||||
ctx.institution = {
|
||||
fetchV1DataPromise: sinon.stub().resolves(ctx.institutionWithV1Data),
|
||||
}
|
||||
this.InstitutionModel = {
|
||||
ctx.InstitutionModel = {
|
||||
Institution: {
|
||||
findOne: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves(this.institution),
|
||||
exec: sinon.stub().resolves(ctx.institution),
|
||||
}),
|
||||
},
|
||||
}
|
||||
this.subscriptionExec = sinon.stub().resolves()
|
||||
ctx.subscriptionExec = sinon.stub().resolves()
|
||||
const SubscriptionModel = {
|
||||
Subscription: {
|
||||
find: () => ({
|
||||
populate: () => ({
|
||||
exec: this.subscriptionExec,
|
||||
exec: ctx.subscriptionExec,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
this.Mongo = {
|
||||
ctx.Mongo = {
|
||||
ObjectId,
|
||||
}
|
||||
|
||||
this.v1Counts = {
|
||||
user_ids: this.users.map(user => user._id),
|
||||
ctx.v1Counts = {
|
||||
user_ids: ctx.users.map(user => user._id),
|
||||
current_users_count: 3,
|
||||
lapsed_user_ids: [lapsedUser._id],
|
||||
entitled_via_sso: 1, // 2 entitled, but 1 lapsed
|
||||
with_confirmed_email: 2, // 1 non entitled SSO + 1 email user
|
||||
}
|
||||
|
||||
this.InstitutionsManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./InstitutionsAPI': {
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Institutions/InstitutionsAPI',
|
||||
() => ({
|
||||
default: {
|
||||
promises: {
|
||||
addAffiliation: (this.addAffiliationPromise = sinon
|
||||
addAffiliation: (ctx.addAffiliationPromise = sinon
|
||||
.stub()
|
||||
.resolves()),
|
||||
getInstitutionAffiliations:
|
||||
(this.getInstitutionAffiliationsPromise = sinon
|
||||
.stub()
|
||||
.resolves(this.affiliations)),
|
||||
getInstitutionAffiliations: (ctx.getInstitutionAffiliationsPromise =
|
||||
sinon.stub().resolves(ctx.affiliations)),
|
||||
getConfirmedInstitutionAffiliations:
|
||||
(this.getConfirmedInstitutionAffiliationsPromise = sinon
|
||||
(ctx.getConfirmedInstitutionAffiliationsPromise = sinon
|
||||
.stub()
|
||||
.resolves(this.affiliations)),
|
||||
.resolves(ctx.affiliations)),
|
||||
getInstitutionAffiliationsCounts:
|
||||
(this.getInstitutionAffiliationsCounts = sinon
|
||||
(ctx.getInstitutionAffiliationsCounts = sinon
|
||||
.stub()
|
||||
.resolves(this.v1Counts)),
|
||||
.resolves(ctx.v1Counts)),
|
||||
},
|
||||
},
|
||||
'../Subscription/FeaturesUpdater': {
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/FeaturesUpdater',
|
||||
() => ({
|
||||
default: {
|
||||
promises: {
|
||||
refreshFeatures: (this.refreshFeaturesPromise = sinon
|
||||
refreshFeatures: (ctx.refreshFeaturesPromise = sinon
|
||||
.stub()
|
||||
.resolves()),
|
||||
},
|
||||
},
|
||||
'../Subscription/FeaturesHelper': {
|
||||
isFeatureSetBetter: (this.isFeatureSetBetter = sinon.stub()),
|
||||
},
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||
'../Subscription/SubscriptionLocator': this.SubscriptionLocator,
|
||||
'../../models/Institution': this.InstitutionModel,
|
||||
'../../models/Subscription': SubscriptionModel,
|
||||
'mongodb-legacy': this.Mongo,
|
||||
'@overleaf/settings': {
|
||||
features: { professional: { 'test-feature': true } },
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/FeaturesHelper',
|
||||
() => ({
|
||||
default: {
|
||||
isFeatureSetBetter: (ctx.isFeatureSetBetter = sinon.stub()),
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Notifications/NotificationsBuilder',
|
||||
() => ({
|
||||
default: ctx.NotificationsBuilder,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Subscription/SubscriptionLocator',
|
||||
() => ({
|
||||
default: ctx.SubscriptionLocator,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/models/Institution',
|
||||
() => ctx.InstitutionModel
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/models/Subscription',
|
||||
() => SubscriptionModel
|
||||
)
|
||||
|
||||
vi.doMock('mongodb-legacy', () => ({
|
||||
default: ctx.Mongo,
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: {
|
||||
features: { professional: { 'test-feature': true } },
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
ctx.InstitutionsManager = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('refreshInstitutionUsers', function () {
|
||||
beforeEach(function () {
|
||||
this.user1Id = '123abc123abc123abc123abc'
|
||||
this.user2Id = '456def456def456def456def'
|
||||
this.user3Id = '789abd789abd789abd789abd'
|
||||
this.user4Id = '321cba321cba321cba321cba'
|
||||
this.affiliations = [
|
||||
{ user_id: this.user1Id },
|
||||
{ user_id: this.user2Id },
|
||||
{ user_id: this.user3Id },
|
||||
{ user_id: this.user4Id },
|
||||
beforeEach(function (ctx) {
|
||||
ctx.user1Id = '123abc123abc123abc123abc'
|
||||
ctx.user2Id = '456def456def456def456def'
|
||||
ctx.user3Id = '789abd789abd789abd789abd'
|
||||
ctx.user4Id = '321cba321cba321cba321cba'
|
||||
ctx.affiliations = [
|
||||
{ user_id: ctx.user1Id },
|
||||
{ user_id: ctx.user2Id },
|
||||
{ user_id: ctx.user3Id },
|
||||
{ user_id: ctx.user4Id },
|
||||
]
|
||||
this.user1 = { _id: this.user1Id }
|
||||
this.user2 = { _id: this.user2Id }
|
||||
this.user3 = { _id: this.user3Id }
|
||||
this.user4 = { _id: this.user4Id }
|
||||
ctx.user1 = { _id: ctx.user1Id }
|
||||
ctx.user2 = { _id: ctx.user2Id }
|
||||
ctx.user3 = { _id: ctx.user3Id }
|
||||
ctx.user4 = { _id: ctx.user4Id }
|
||||
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(this.user1Id))
|
||||
.resolves(this.user1)
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(this.user2Id))
|
||||
.resolves(this.user2)
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(this.user3Id))
|
||||
.resolves(this.user3)
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(this.user4Id))
|
||||
.resolves(this.user4)
|
||||
ctx.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(ctx.user1Id))
|
||||
.resolves(ctx.user1)
|
||||
ctx.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(ctx.user2Id))
|
||||
.resolves(ctx.user2)
|
||||
ctx.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(ctx.user3Id))
|
||||
.resolves(ctx.user3)
|
||||
ctx.UserGetter.promises.getUser
|
||||
.withArgs(new ObjectId(ctx.user4Id))
|
||||
.resolves(ctx.user4)
|
||||
|
||||
this.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(this.user2)
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(ctx.user2)
|
||||
.resolves({
|
||||
planCode: 'pro',
|
||||
groupPlan: false,
|
||||
})
|
||||
this.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(this.user3)
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(ctx.user3)
|
||||
.resolves({
|
||||
planCode: 'collaborator_free_trial_7_days',
|
||||
groupPlan: false,
|
||||
})
|
||||
this.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(this.user4)
|
||||
ctx.SubscriptionLocator.promises.getUsersSubscription
|
||||
.withArgs(ctx.user4)
|
||||
.resolves({
|
||||
planCode: 'collaborator-annual',
|
||||
groupPlan: true,
|
||||
})
|
||||
|
||||
this.refreshFeaturesPromise.resolves({
|
||||
ctx.refreshFeaturesPromise.resolves({
|
||||
newFeatures: {},
|
||||
featuresChanged: false,
|
||||
})
|
||||
this.refreshFeaturesPromise
|
||||
.withArgs(new ObjectId(this.user1Id))
|
||||
ctx.refreshFeaturesPromise
|
||||
.withArgs(new ObjectId(ctx.user1Id))
|
||||
.resolves({ newFeatures: {}, featuresChanged: true })
|
||||
this.getInstitutionAffiliationsPromise.resolves(this.affiliations)
|
||||
this.getConfirmedInstitutionAffiliationsPromise.resolves(
|
||||
this.affiliations
|
||||
)
|
||||
ctx.getInstitutionAffiliationsPromise.resolves(ctx.affiliations)
|
||||
ctx.getConfirmedInstitutionAffiliationsPromise.resolves(ctx.affiliations)
|
||||
})
|
||||
|
||||
it('refresh all users Features', async function () {
|
||||
await this.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
this.institutionId,
|
||||
it('refresh all users Features', async function (ctx) {
|
||||
await ctx.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
ctx.institutionId,
|
||||
false
|
||||
)
|
||||
sinon.assert.callCount(this.refreshFeaturesPromise, 4)
|
||||
sinon.assert.callCount(ctx.refreshFeaturesPromise, 4)
|
||||
// expect no notifications
|
||||
sinon.assert.notCalled(
|
||||
this.NotificationsBuilder.promises.featuresUpgradedByAffiliation
|
||||
ctx.NotificationsBuilder.promises.featuresUpgradedByAffiliation
|
||||
)
|
||||
sinon.assert.notCalled(
|
||||
this.NotificationsBuilder.promises.redundantPersonalSubscription
|
||||
ctx.NotificationsBuilder.promises.redundantPersonalSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('notifies users if their features have been upgraded', async function () {
|
||||
await this.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
this.institutionId,
|
||||
it('notifies users if their features have been upgraded', async function (ctx) {
|
||||
await ctx.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
ctx.institutionId,
|
||||
true
|
||||
)
|
||||
sinon.assert.calledOnce(
|
||||
this.NotificationsBuilder.promises.featuresUpgradedByAffiliation
|
||||
ctx.NotificationsBuilder.promises.featuresUpgradedByAffiliation
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.NotificationsBuilder.promises.featuresUpgradedByAffiliation,
|
||||
this.affiliations[0],
|
||||
this.user1
|
||||
ctx.NotificationsBuilder.promises.featuresUpgradedByAffiliation,
|
||||
ctx.affiliations[0],
|
||||
ctx.user1
|
||||
)
|
||||
})
|
||||
|
||||
it('notifies users if they have a subscription, or a trial subscription, that should be cancelled', async function () {
|
||||
await this.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
this.institutionId,
|
||||
it('notifies users if they have a subscription, or a trial subscription, that should be cancelled', async function (ctx) {
|
||||
await ctx.InstitutionsManager.promises.refreshInstitutionUsers(
|
||||
ctx.institutionId,
|
||||
true
|
||||
)
|
||||
|
||||
sinon.assert.calledTwice(
|
||||
this.NotificationsBuilder.promises.redundantPersonalSubscription
|
||||
ctx.NotificationsBuilder.promises.redundantPersonalSubscription
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.NotificationsBuilder.promises.redundantPersonalSubscription,
|
||||
this.affiliations[1],
|
||||
this.user2
|
||||
ctx.NotificationsBuilder.promises.redundantPersonalSubscription,
|
||||
ctx.affiliations[1],
|
||||
ctx.user2
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.NotificationsBuilder.promises.redundantPersonalSubscription,
|
||||
this.affiliations[2],
|
||||
this.user3
|
||||
ctx.NotificationsBuilder.promises.redundantPersonalSubscription,
|
||||
ctx.affiliations[2],
|
||||
ctx.user3
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkInstitutionUsers', function () {
|
||||
it('returns entitled/not, sso/not, lapsed/current, and pro counts', async function () {
|
||||
it('returns entitled/not, sso/not, lapsed/current, and pro counts', async function (ctx) {
|
||||
if (Features.hasFeature('saas')) {
|
||||
this.isFeatureSetBetter.returns(true)
|
||||
ctx.isFeatureSetBetter.returns(true)
|
||||
const usersSummary =
|
||||
await this.InstitutionsManager.promises.checkInstitutionUsers(
|
||||
this.institutionId
|
||||
await ctx.InstitutionsManager.promises.checkInstitutionUsers(
|
||||
ctx.institutionId
|
||||
)
|
||||
expect(usersSummary).to.deep.equal({
|
||||
emailUsers: {
|
||||
@@ -300,13 +341,13 @@ describe('InstitutionsManager', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('includes withConfirmedEmailMismatch when v1 and v2 counts do not add up', async function () {
|
||||
it('includes withConfirmedEmailMismatch when v1 and v2 counts do not add up', async function (ctx) {
|
||||
if (Features.hasFeature('saas')) {
|
||||
this.isFeatureSetBetter.returns(true)
|
||||
this.v1Counts.with_confirmed_email = 100
|
||||
ctx.isFeatureSetBetter.returns(true)
|
||||
ctx.v1Counts.with_confirmed_email = 100
|
||||
const usersSummary =
|
||||
await this.InstitutionsManager.promises.checkInstitutionUsers(
|
||||
this.institutionId
|
||||
await ctx.InstitutionsManager.promises.checkInstitutionUsers(
|
||||
ctx.institutionId
|
||||
)
|
||||
expect(usersSummary).to.deep.equal({
|
||||
emailUsers: {
|
||||
@@ -350,116 +391,115 @@ describe('InstitutionsManager', function () {
|
||||
})
|
||||
|
||||
describe('getInstitutionUsersSubscriptions', function () {
|
||||
it('returns all institution users subscriptions', async function () {
|
||||
it('returns all institution users subscriptions', async function (ctx) {
|
||||
const stubbedUsers = [
|
||||
{ user_id: '123abc123abc123abc123abc' },
|
||||
{ user_id: '456def456def456def456def' },
|
||||
{ user_id: '789def789def789def789def' },
|
||||
]
|
||||
this.getInstitutionAffiliationsPromise.resolves(stubbedUsers)
|
||||
await this.InstitutionsManager.promises.getInstitutionUsersSubscriptions(
|
||||
this.institutionId
|
||||
ctx.getInstitutionAffiliationsPromise.resolves(stubbedUsers)
|
||||
await ctx.InstitutionsManager.promises.getInstitutionUsersSubscriptions(
|
||||
ctx.institutionId
|
||||
)
|
||||
sinon.assert.calledOnce(this.subscriptionExec)
|
||||
sinon.assert.calledOnce(ctx.subscriptionExec)
|
||||
})
|
||||
})
|
||||
|
||||
describe('addAffiliations', function () {
|
||||
beforeEach(function () {
|
||||
this.host = 'mit.edu'.split('').reverse().join('')
|
||||
this.stubbedUser1 = {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.host = 'mit.edu'.split('').reverse().join('')
|
||||
ctx.stubbedUser1 = {
|
||||
_id: '6573014d8a14461b3d1aac3f',
|
||||
name: 'bob',
|
||||
email: 'hello@world.com',
|
||||
emails: [
|
||||
{ email: 'stubb1@mit.edu', reversedHostname: this.host },
|
||||
{ email: 'stubb1@mit.edu', reversedHostname: ctx.host },
|
||||
{ email: 'test@test.com', reversedHostname: 'test.com' },
|
||||
{ email: 'another@mit.edu', reversedHostname: this.host },
|
||||
{ email: 'another@mit.edu', reversedHostname: ctx.host },
|
||||
],
|
||||
}
|
||||
this.stubbedUser1DecoratedEmails = [
|
||||
ctx.stubbedUser1DecoratedEmails = [
|
||||
{
|
||||
email: 'stubb1@mit.edu',
|
||||
reversedHostname: this.host,
|
||||
reversedHostname: ctx.host,
|
||||
samlIdentifier: { hasEntitlement: false },
|
||||
},
|
||||
{ email: 'test@test.com', reversedHostname: 'test.com' },
|
||||
{
|
||||
email: 'another@mit.edu',
|
||||
reversedHostname: this.host,
|
||||
reversedHostname: ctx.host,
|
||||
samlIdentifier: { hasEntitlement: true },
|
||||
},
|
||||
]
|
||||
this.stubbedUser2 = {
|
||||
ctx.stubbedUser2 = {
|
||||
_id: '6573014d8a14461b3d1aac40',
|
||||
name: 'test',
|
||||
email: 'hello2@world.com',
|
||||
emails: [{ email: 'subb2@mit.edu', reversedHostname: this.host }],
|
||||
emails: [{ email: 'subb2@mit.edu', reversedHostname: ctx.host }],
|
||||
}
|
||||
this.stubbedUser2DecoratedEmails = [
|
||||
ctx.stubbedUser2DecoratedEmails = [
|
||||
{
|
||||
email: 'subb2@mit.edu',
|
||||
reversedHostname: this.host,
|
||||
reversedHostname: ctx.host,
|
||||
},
|
||||
]
|
||||
|
||||
this.getInstitutionUsersByHostname = sinon.stub().resolves([
|
||||
ctx.getInstitutionUsersByHostname = sinon.stub().resolves([
|
||||
{
|
||||
_id: this.stubbedUser1._id,
|
||||
emails: this.stubbedUser1DecoratedEmails,
|
||||
_id: ctx.stubbedUser1._id,
|
||||
emails: ctx.stubbedUser1DecoratedEmails,
|
||||
},
|
||||
{
|
||||
_id: this.stubbedUser2._id,
|
||||
emails: this.stubbedUser2DecoratedEmails,
|
||||
_id: ctx.stubbedUser2._id,
|
||||
emails: ctx.stubbedUser2DecoratedEmails,
|
||||
},
|
||||
])
|
||||
this.UserGetter.promises.getInstitutionUsersByHostname =
|
||||
this.getInstitutionUsersByHostname
|
||||
ctx.UserGetter.promises.getInstitutionUsersByHostname =
|
||||
ctx.getInstitutionUsersByHostname
|
||||
})
|
||||
|
||||
describe('affiliateUsers', function () {
|
||||
it('should add affiliations for matching users', async function () {
|
||||
await this.InstitutionsManager.promises.affiliateUsers('mit.edu')
|
||||
it('should add affiliations for matching users', async function (ctx) {
|
||||
await ctx.InstitutionsManager.promises.affiliateUsers('mit.edu')
|
||||
|
||||
this.getInstitutionUsersByHostname.calledOnce.should.equal(true)
|
||||
this.addAffiliationPromise.calledThrice.should.equal(true)
|
||||
this.addAffiliationPromise
|
||||
ctx.getInstitutionUsersByHostname.calledOnce.should.equal(true)
|
||||
ctx.addAffiliationPromise.calledThrice.should.equal(true)
|
||||
ctx.addAffiliationPromise
|
||||
.calledWithMatch(
|
||||
this.stubbedUser1._id,
|
||||
this.stubbedUser1.emails[0].email,
|
||||
ctx.stubbedUser1._id,
|
||||
ctx.stubbedUser1.emails[0].email,
|
||||
{ entitlement: false }
|
||||
)
|
||||
.should.equal(true)
|
||||
this.addAffiliationPromise
|
||||
ctx.addAffiliationPromise
|
||||
.calledWithMatch(
|
||||
this.stubbedUser1._id,
|
||||
this.stubbedUser1.emails[2].email,
|
||||
ctx.stubbedUser1._id,
|
||||
ctx.stubbedUser1.emails[2].email,
|
||||
{ entitlement: true }
|
||||
)
|
||||
.should.equal(true)
|
||||
this.addAffiliationPromise
|
||||
ctx.addAffiliationPromise
|
||||
.calledWithMatch(
|
||||
this.stubbedUser2._id,
|
||||
this.stubbedUser2.emails[0].email,
|
||||
ctx.stubbedUser2._id,
|
||||
ctx.stubbedUser2.emails[0].email,
|
||||
{ entitlement: undefined }
|
||||
)
|
||||
.should.equal(true)
|
||||
this.refreshFeaturesPromise
|
||||
.calledWith(this.stubbedUser1._id)
|
||||
ctx.refreshFeaturesPromise
|
||||
.calledWith(ctx.stubbedUser1._id)
|
||||
.should.equal(true)
|
||||
this.refreshFeaturesPromise
|
||||
.calledWith(this.stubbedUser2._id)
|
||||
ctx.refreshFeaturesPromise
|
||||
.calledWith(ctx.stubbedUser2._id)
|
||||
.should.equal(true)
|
||||
this.refreshFeaturesPromise.should.have.been.calledTwice
|
||||
ctx.refreshFeaturesPromise.should.have.been.calledTwice
|
||||
})
|
||||
|
||||
it('should return errors if last affiliation cannot be added', async function () {
|
||||
this.addAffiliationPromise.onCall(2).rejects()
|
||||
await expect(
|
||||
this.InstitutionsManager.promises.affiliateUsers('mit.edu')
|
||||
).to.be.rejected
|
||||
it('should return errors if last affiliation cannot be added', async function (ctx) {
|
||||
ctx.addAffiliationPromise.onCall(2).rejects()
|
||||
await expect(ctx.InstitutionsManager.promises.affiliateUsers('mit.edu'))
|
||||
.to.be.rejected
|
||||
|
||||
this.getInstitutionUsersByHostname.calledOnce.should.equal(true)
|
||||
ctx.getInstitutionUsersByHostname.calledOnce.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,69 +1,77 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/Subscription/SubscriptionEmailHandler'
|
||||
|
||||
describe('SubscriptionEmailHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.userId = '123456789abcde'
|
||||
this.email = 'test@test.com'
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.userId = '123456789abcde'
|
||||
ctx.email = 'test@test.com'
|
||||
|
||||
this.SubscriptionEmailHandler = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../Email/EmailHandler': (this.EmailHandler = {
|
||||
promises: {
|
||||
sendEmail: sinon.stub().resolves({}),
|
||||
},
|
||||
vi.doMock('../../../../app/src/Features/Email/EmailHandler', () => ({
|
||||
default: (ctx.EmailHandler = {
|
||||
promises: {
|
||||
sendEmail: sinon.stub().resolves({}),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: (ctx.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon
|
||||
.stub()
|
||||
.resolves({ _id: ctx.userId, email: 'test@test.com' }),
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Subscription/PlansLocator', () => ({
|
||||
default: (ctx.PlansLocator = {
|
||||
findLocalPlanInSettings: sinon.stub().returns({
|
||||
name: 'foo',
|
||||
features: { collaborators: 42 },
|
||||
}),
|
||||
'../User/UserGetter': (this.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon
|
||||
.stub()
|
||||
.resolves({ _id: this.userId, email: 'test@test.com' }),
|
||||
},
|
||||
}),
|
||||
'./PlansLocator': (this.PlansLocator = {
|
||||
findLocalPlanInSettings: sinon.stub().returns({
|
||||
name: 'foo',
|
||||
features: { collaborators: 42 },
|
||||
}),
|
||||
}),
|
||||
'@overleaf/settings': (this.Settings = {
|
||||
enableOnboardingEmails: true,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.Settings = {
|
||||
enableOnboardingEmails: true,
|
||||
}),
|
||||
}))
|
||||
|
||||
ctx.SubscriptionEmailHandler = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
describe('when onboarding emails are disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Settings.enableOnboardingEmails = false
|
||||
beforeEach(function (ctx) {
|
||||
ctx.Settings.enableOnboardingEmails = false
|
||||
})
|
||||
it('does not send a trial onboarding email', async function () {
|
||||
await this.SubscriptionEmailHandler.sendTrialOnboardingEmail(
|
||||
this.userId,
|
||||
it('does not send a trial onboarding email', async function (ctx) {
|
||||
await ctx.SubscriptionEmailHandler.sendTrialOnboardingEmail(
|
||||
ctx.userId,
|
||||
'foo-plan-code'
|
||||
)
|
||||
expect(this.EmailHandler.promises.sendEmail).to.not.have.been.called
|
||||
expect(ctx.EmailHandler.promises.sendEmail).to.not.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
describe('when onboarding emails are enabled', function () {
|
||||
it('sends trial onboarding email', async function () {
|
||||
await this.SubscriptionEmailHandler.sendTrialOnboardingEmail(
|
||||
this.userId,
|
||||
it('sends trial onboarding email', async function (ctx) {
|
||||
await ctx.SubscriptionEmailHandler.sendTrialOnboardingEmail(
|
||||
ctx.userId,
|
||||
'foo-plan-code'
|
||||
)
|
||||
|
||||
expect(this.PlansLocator.findLocalPlanInSettings).to.have.been.calledWith(
|
||||
expect(ctx.PlansLocator.findLocalPlanInSettings).to.have.been.calledWith(
|
||||
'foo-plan-code'
|
||||
)
|
||||
expect(this.EmailHandler.promises.sendEmail.lastCall.args).to.deep.equal([
|
||||
expect(ctx.EmailHandler.promises.sendEmail.lastCall.args).to.deep.equal([
|
||||
'trialOnboarding',
|
||||
{
|
||||
to: this.email,
|
||||
sendingUser_id: this.userId,
|
||||
to: ctx.email,
|
||||
sendingUser_id: ctx.userId,
|
||||
planName: 'foo',
|
||||
features: { collaborators: 42 },
|
||||
},
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const chai = require('chai')
|
||||
const SubscriptionFormatters = require('../../../../app/src/Features/Subscription/SubscriptionFormatters')
|
||||
|
||||
const { expect } = chai
|
||||
import { expect } from 'vitest'
|
||||
import SubscriptionFormatters from '../../../../app/src/Features/Subscription/SubscriptionFormatters.mjs'
|
||||
|
||||
describe('SubscriptionFormatters', function () {
|
||||
describe('formatDateTime', function () {
|
||||
|
||||
Reference in New Issue
Block a user