[web] Promisify ProjectController (#18477)

* Create `promiseAuto` util to replace `async.auto`

* Promisify `BrandVariationsHandler.getBrandVariationById`

* Promisify `updateProjectSettings`

* Promisify `updateProjectAdminSettings`

* Promisify `newProject`

* Promisify `deleteProject`

* Promisify `loadEditor`

* Fix brandVariation loading in promise auto

* Promisify `_refreshFeatures`

* Promisify `_injectProjectUsers`

* Fix `no-inner-declarations`

* Promisify `cloneProject`

* Promisify `userProjectsJson`

* Promisify `projectEntitiesJson`

* Promisify `restoreProject`

* Promisify `renameProject`

* Additional warning fix

* Update unit tests

* Fixup `updateProjectSettings`: call jobs inside the Promise.all

* Use `expressify(...)` instead of manually call `next(err)`

https://github.com/overleaf/internal/pull/18477#discussion_r1613611987
https://github.com/overleaf/internal/pull/18477#discussion_r1613621146
https://github.com/overleaf/internal/pull/18477#discussion_r1613634000
...

* Replace Promise.all by sequencial awaits

https://github.com/overleaf/internal/pull/18477#discussion_r1613852746
https://github.com/overleaf/internal/pull/18477#discussion_r1613611987

* Remove manual throws of 500. Let the generic error handler catch them.

https://github.com/overleaf/internal/pull/18477#discussion_r1613623446
https://github.com/overleaf/internal/pull/18477#discussion_r1613628955

* Promisify `untrashProject`

https://github.com/overleaf/internal/pull/18477#discussion_r1613627783

* Promisify `expireDeletedProjectsAfterDuration`

* Promisify `archiveProject`

* Promisify `unarchiveProject`

* Promisify `trashProject`

* Promisify `expireDeletedProject`

* Use async `setTimeout` from `timers/promise`

https://github.com/overleaf/internal/pull/18477#discussion_r1613843085

* Remove unused `_injectProjectUsers`

https://github.com/overleaf/internal/pull/18477#discussion_r1613855766

* Add missing exec in queries (?)

Not sure if that makes a real difference but it's more consistent with the rest of the code

* Catch floating promises

https://github.com/overleaf/internal/pull/18477#discussion_r1613868876

* Replace custom `promiseAuto` by `p-props` from NPM

https://github.com/overleaf/internal/pull/18477#discussion_r1613393294

* Downgrade `p-props` to v4. Later versions require ESM

* Simplify code around `splitTestAssignments`

GitOrigin-RevId: 84d37f7aa9227b5b9acf9eeb5db1b78afc01b6ee
This commit is contained in:
Antoine Clausse 2024-05-29 14:19:10 +02:00 committed by Copybot
parent 01e1286a8b
commit 36f0a3e01a
7 changed files with 712 additions and 817 deletions

2
package-lock.json generated
View file

@ -44498,6 +44498,7 @@
"openai": "^4.36.0", "openai": "^4.36.0",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"p-limit": "^2.3.0", "p-limit": "^2.3.0",
"p-props": "4.0.0",
"parse-data-url": "^2.0.0", "parse-data-url": "^2.0.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
@ -53119,6 +53120,7 @@
"openai": "^4.36.0", "openai": "^4.36.0",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"p-limit": "^2.3.0", "p-limit": "^2.3.0",
"p-props": "4.0.0",
"parse-data-url": "^2.0.0", "parse-data-url": "^2.0.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",

View file

@ -70,6 +70,7 @@ async function getPublicAccessLevel(projectId) {
* *
* @param userId - The id of the user that wants to access the project. * @param userId - The id of the user that wants to access the project.
* @param projectId - The id of the project to be accessed. * @param projectId - The id of the project to be accessed.
* @param {string} token
* @param {Object} opts * @param {Object} opts
* @param {boolean} opts.ignoreSiteAdmin - Do not consider whether the user is * @param {boolean} opts.ignoreSiteAdmin - Do not consider whether the user is
* a site admin. * a site admin.

View file

@ -4,9 +4,13 @@ const settings = require('@overleaf/settings')
const logger = require('@overleaf/logger') const logger = require('@overleaf/logger')
const V1Api = require('../V1/V1Api') const V1Api = require('../V1/V1Api')
const sanitizeHtml = require('sanitize-html') const sanitizeHtml = require('sanitize-html')
const { promisify } = require('@overleaf/promise-utils')
module.exports = { module.exports = {
getBrandVariationById, getBrandVariationById,
promises: {
getBrandVariationById: promisify(getBrandVariationById),
},
} }
function getBrandVariationById(brandVariationId, callback) { function getBrandVariationById(brandVariationId, callback) {

File diff suppressed because it is too large Load diff

View file

@ -142,6 +142,7 @@
"openai": "^4.36.0", "openai": "^4.36.0",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"p-limit": "^2.3.0", "p-limit": "^2.3.0",
"p-props": "4.0.0",
"parse-data-url": "^2.0.0", "parse-data-url": "^2.0.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",

View file

@ -50,6 +50,7 @@ SandboxedModule.configure({
ignoreMissing: true, ignoreMissing: true,
requires: getSandboxedModuleRequires(), requires: getSandboxedModuleRequires(),
globals: { globals: {
AbortController,
AbortSignal, AbortSignal,
Buffer, Buffer,
Promise, Promise,

View file

@ -38,48 +38,73 @@ describe('ProjectController', function () {
} }
this.token = 'some-token' this.token = 'some-token'
this.ProjectDeleter = { this.ProjectDeleter = {
deleteProject: sinon.stub().callsArg(2), promises: {
restoreProject: sinon.stub().callsArg(1), deleteProject: sinon.stub().resolves(),
restoreProject: sinon.stub().resolves(),
},
findArchivedProjects: sinon.stub(), findArchivedProjects: sinon.stub(),
} }
this.ProjectDuplicator = { this.ProjectDuplicator = {
duplicate: sinon.stub().callsArgWith(4, null, { _id: this.project_id }), promises: {
duplicate: sinon.stub().resolves({ _id: this.project_id }),
},
} }
this.ProjectCreationHandler = { this.ProjectCreationHandler = {
createExampleProject: sinon promises: {
.stub() createExampleProject: sinon.stub().resolves({ _id: this.project_id }),
.callsArgWith(2, null, { _id: this.project_id }), createBasicProject: sinon.stub().resolves({ _id: this.project_id }),
createBasicProject: sinon },
.stub() }
.callsArgWith(2, null, { _id: this.project_id }), this.SubscriptionLocator = {
promises: {
getUsersSubscription: sinon.stub().resolves(),
},
} }
this.SubscriptionLocator = { getUsersSubscription: sinon.stub() }
this.LimitationsManager = { this.LimitationsManager = {
hasPaidSubscription: sinon.stub(), hasPaidSubscription: sinon.stub(),
userIsMemberOfGroupSubscription: sinon promises: {
.stub() userIsMemberOfGroupSubscription: sinon.stub().resolves(false),
.callsArgWith(1, null, false), },
} }
this.TagsHandler = { this.TagsHandler = {
getTagsForProject: sinon.stub().callsArgWith(2, null, [ promises: {
{ getTagsForProject: sinon.stub().resolves([
name: 'test', {
project_ids: [this.project_id], name: 'test',
}, project_ids: [this.project_id],
]), },
]),
},
addProjectToTags: sinon.stub(), addProjectToTags: sinon.stub(),
} }
this.UserModel = { findById: sinon.stub(), updateOne: sinon.stub() } this.UserModel = {
findById: sinon.stub().returns({ exec: sinon.stub().resolves() }),
updateOne: sinon.stub().returns({ exec: sinon.stub().resolves() }),
}
this.AuthorizationManager = { this.AuthorizationManager = {
getPrivilegeLevelForProject: sinon.stub(), promises: {
getPrivilegeLevelForProject: sinon.stub(),
},
isRestrictedUser: sinon.stub().returns(false), isRestrictedUser: sinon.stub().returns(false),
} }
this.EditorController = { renameProject: sinon.stub() } this.EditorController = {
this.InactiveProjectManager = { reactivateProjectIfRequired: sinon.stub() } promises: {
this.ProjectUpdateHandler = { markAsOpened: sinon.stub() } renameProject: sinon.stub().resolves(),
},
}
this.InactiveProjectManager = {
promises: { reactivateProjectIfRequired: sinon.stub() },
}
this.ProjectUpdateHandler = {
promises: {
markAsOpened: sinon.stub().resolves(),
},
}
this.ProjectGetter = { this.ProjectGetter = {
findAllUsersProjects: sinon.stub(), promises: {
getProject: sinon.stub(), findAllUsersProjects: sinon.stub().resolves(),
getProject: sinon.stub().resolves(),
},
} }
this.ProjectHelper = { this.ProjectHelper = {
isArchived: sinon.stub(), isArchived: sinon.stub(),
@ -88,7 +113,6 @@ describe('ProjectController', function () {
getAllowedImagesForUser: sinon.stub().returns([]), getAllowedImagesForUser: sinon.stub().returns([]),
} }
this.SessionManager = { this.SessionManager = {
getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user),
getLoggedInUserId: sinon.stub().returns(this.user._id), getLoggedInUserId: sinon.stub().returns(this.user._id),
getSessionUser: sinon.stub().returns(this.user), getSessionUser: sinon.stub().returns(this.user),
isUserLoggedIn: sinon.stub().returns(true), isUserLoggedIn: sinon.stub().returns(true),
@ -100,30 +124,36 @@ describe('ProjectController', function () {
getRequestToken: sinon.stub().returns(this.token), getRequestToken: sinon.stub().returns(this.token),
} }
this.CollaboratorsGetter = { this.CollaboratorsGetter = {
userIsTokenMember: sinon.stub().callsArgWith(2, null, false), promises: {
isUserInvitedMemberOfProject: sinon.stub().callsArgWith(2, null, true), userIsTokenMember: sinon.stub().resolves(false),
isUserInvitedMemberOfProject: sinon.stub().resolves(true),
},
} }
this.ProjectEntityHandler = {} this.ProjectEntityHandler = {}
this.UserGetter = { this.UserGetter = {
getUserFullEmails: sinon.stub().yields(null, []), getUserFullEmails: sinon.stub().yields(null, []),
getUser: sinon getUser: sinon.stub().resolves({ lastLoginIp: '192.170.18.2' }),
.stub()
.callsArgWith(2, null, { lastLoginIp: '192.170.18.2' }),
} }
this.Features = { this.Features = {
hasFeature: sinon.stub(), hasFeature: sinon.stub(),
} }
this.FeaturesUpdater = { this.FeaturesUpdater = {
featuresEpochIsCurrent: sinon.stub().returns(true), featuresEpochIsCurrent: sinon.stub().returns(true),
refreshFeatures: sinon.stub().yields(null, this.user), promises: {
refreshFeatures: sinon.stub().resolves(this.user),
},
} }
this.BrandVariationsHandler = { this.BrandVariationsHandler = {
getBrandVariationById: sinon promises: {
.stub() getBrandVariationById: sinon
.callsArgWith(1, null, this.brandVariationDetails), .stub()
.resolves(this.brandVariationDetails),
},
} }
this.TpdsProjectFlusher = { this.TpdsProjectFlusher = {
flushProjectToTpdsIfNeeded: sinon.stub().yields(), promises: {
flushProjectToTpdsIfNeeded: sinon.stub().resolves(),
},
} }
this.Metrics = { this.Metrics = {
Timer: class { Timer: class {
@ -138,10 +168,14 @@ describe('ProjectController', function () {
getAssignment: sinon.stub().yields(null, { variant: 'default' }), getAssignment: sinon.stub().yields(null, { variant: 'default' }),
} }
this.SplitTestSessionHandler = { this.SplitTestSessionHandler = {
sessionMaintenance: sinon.stub().yields(), promises: {
sessionMaintenance: sinon.stub().resolves(),
},
} }
this.InstitutionsFeatures = { this.InstitutionsFeatures = {
hasLicence: sinon.stub().callsArgWith(1, null, false), promises: {
hasLicence: sinon.stub().resolves(false),
},
} }
this.SubscriptionViewModelBuilder = { this.SubscriptionViewModelBuilder = {
getBestSubscription: sinon.stub().yields(null, { type: 'free' }), getBestSubscription: sinon.stub().yields(null, { type: 'free' }),
@ -150,7 +184,9 @@ describe('ProjectController', function () {
getSurvey: sinon.stub().yields(null, {}), getSurvey: sinon.stub().yields(null, {}),
} }
this.ProjectAuditLogHandler = { this.ProjectAuditLogHandler = {
addEntry: sinon.stub().yields(), promises: {
addEntry: sinon.stub().resolves(),
},
} }
this.TutorialHandler = { this.TutorialHandler = {
getInactiveTutorials: sinon.stub().returns([]), getInactiveTutorials: sinon.stub().returns([]),
@ -195,7 +231,9 @@ describe('ProjectController', function () {
'../Subscription/SubscriptionViewModelBuilder': '../Subscription/SubscriptionViewModelBuilder':
this.SubscriptionViewModelBuilder, this.SubscriptionViewModelBuilder,
'../Spelling/SpellingHandler': { '../Spelling/SpellingHandler': {
getUserDictionary: sinon.stub().yields(null, []), promises: {
getUserDictionary: sinon.stub().resolves([]),
},
}, },
'../Institutions/InstitutionsFeatures': this.InstitutionsFeatures, '../Institutions/InstitutionsFeatures': this.InstitutionsFeatures,
'../Survey/SurveyHandler': this.SurveyHandler, '../Survey/SurveyHandler': this.SurveyHandler,
@ -235,10 +273,10 @@ describe('ProjectController', function () {
describe('updateProjectSettings', function () { describe('updateProjectSettings', function () {
it('should update the name', function (done) { it('should update the name', function (done) {
this.EditorController.renameProject = sinon.stub().callsArg(2) this.EditorController.promises.renameProject = sinon.stub().resolves()
this.req.body = { name: (this.name = 'New name') } this.req.body = { name: (this.name = 'New name') }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.renameProject this.EditorController.promises.renameProject
.calledWith(this.project_id, this.name) .calledWith(this.project_id, this.name)
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -248,10 +286,10 @@ describe('ProjectController', function () {
}) })
it('should update the compiler', function (done) { it('should update the compiler', function (done) {
this.EditorController.setCompiler = sinon.stub().callsArg(2) this.EditorController.promises.setCompiler = sinon.stub().resolves()
this.req.body = { compiler: (this.compiler = 'pdflatex') } this.req.body = { compiler: (this.compiler = 'pdflatex') }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.setCompiler this.EditorController.promises.setCompiler
.calledWith(this.project_id, this.compiler) .calledWith(this.project_id, this.compiler)
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -261,10 +299,10 @@ describe('ProjectController', function () {
}) })
it('should update the imageName', function (done) { it('should update the imageName', function (done) {
this.EditorController.setImageName = sinon.stub().callsArg(2) this.EditorController.promises.setImageName = sinon.stub().resolves()
this.req.body = { imageName: (this.imageName = 'texlive-1234.5') } this.req.body = { imageName: (this.imageName = 'texlive-1234.5') }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.setImageName this.EditorController.promises.setImageName
.calledWith(this.project_id, this.imageName) .calledWith(this.project_id, this.imageName)
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -274,10 +312,12 @@ describe('ProjectController', function () {
}) })
it('should update the spell check language', function (done) { it('should update the spell check language', function (done) {
this.EditorController.setSpellCheckLanguage = sinon.stub().callsArg(2) this.EditorController.promises.setSpellCheckLanguage = sinon
.stub()
.resolves()
this.req.body = { spellCheckLanguage: (this.languageCode = 'fr') } this.req.body = { spellCheckLanguage: (this.languageCode = 'fr') }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.setSpellCheckLanguage this.EditorController.promises.setSpellCheckLanguage
.calledWith(this.project_id, this.languageCode) .calledWith(this.project_id, this.languageCode)
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -287,10 +327,10 @@ describe('ProjectController', function () {
}) })
it('should update the root doc', function (done) { it('should update the root doc', function (done) {
this.EditorController.setRootDoc = sinon.stub().callsArg(2) this.EditorController.promises.setRootDoc = sinon.stub().resolves()
this.req.body = { rootDocId: (this.rootDocId = 'root-doc-id') } this.req.body = { rootDocId: (this.rootDocId = 'root-doc-id') }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.setRootDoc this.EditorController.promises.setRootDoc
.calledWith(this.project_id, this.rootDocId) .calledWith(this.project_id, this.rootDocId)
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -302,12 +342,14 @@ describe('ProjectController', function () {
describe('updateProjectAdminSettings', function () { describe('updateProjectAdminSettings', function () {
it('should update the public access level', function (done) { it('should update the public access level', function (done) {
this.EditorController.setPublicAccessLevel = sinon.stub().callsArg(2) this.EditorController.promises.setPublicAccessLevel = sinon
.stub()
.resolves()
this.req.body = { this.req.body = {
publicAccessLevel: 'readOnly', publicAccessLevel: 'readOnly',
} }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.EditorController.setPublicAccessLevel this.EditorController.promises.setPublicAccessLevel
.calledWith(this.project_id, 'readOnly') .calledWith(this.project_id, 'readOnly')
.should.equal(true) .should.equal(true)
code.should.equal(204) code.should.equal(204)
@ -317,12 +359,14 @@ describe('ProjectController', function () {
}) })
it('should record the change in the project audit log', function (done) { it('should record the change in the project audit log', function (done) {
this.EditorController.setPublicAccessLevel = sinon.stub().callsArg(2) this.EditorController.promises.setPublicAccessLevel = sinon
.stub()
.resolves()
this.req.body = { this.req.body = {
publicAccessLevel: 'readOnly', publicAccessLevel: 'readOnly',
} }
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.ProjectAuditLogHandler.addEntry this.ProjectAuditLogHandler.promises.addEntry
.calledWith( .calledWith(
this.project_id, this.project_id,
'toggle-access-level', 'toggle-access-level',
@ -343,7 +387,7 @@ describe('ProjectController', function () {
describe('deleteProject', function () { describe('deleteProject', function () {
it('should call the project deleter', function (done) { it('should call the project deleter', function (done) {
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.ProjectDeleter.deleteProject this.ProjectDeleter.promises.deleteProject
.calledWith(this.project_id, { .calledWith(this.project_id, {
deleterUser: this.user, deleterUser: this.user,
ipAddress: this.req.ip, ipAddress: this.req.ip,
@ -359,7 +403,7 @@ describe('ProjectController', function () {
describe('restoreProject', function () { describe('restoreProject', function () {
it('should tell the project deleter', function (done) { it('should tell the project deleter', function (done) {
this.res.sendStatus = code => { this.res.sendStatus = code => {
this.ProjectDeleter.restoreProject this.ProjectDeleter.promises.restoreProject
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true) .should.equal(true)
code.should.equal(200) code.should.equal(200)
@ -372,7 +416,7 @@ describe('ProjectController', function () {
describe('cloneProject', function () { describe('cloneProject', function () {
it('should call the project duplicator', function (done) { it('should call the project duplicator', function (done) {
this.res.json = json => { this.res.json = json => {
this.ProjectDuplicator.duplicate this.ProjectDuplicator.promises.duplicate
.calledWith(this.user, this.project_id, this.projectName) .calledWith(this.user, this.project_id, this.projectName)
.should.equal(true) .should.equal(true)
json.project_id.should.equal(this.project_id) json.project_id.should.equal(this.project_id)
@ -386,10 +430,10 @@ describe('ProjectController', function () {
it('should call the projectCreationHandler with createExampleProject', function (done) { it('should call the projectCreationHandler with createExampleProject', function (done) {
this.req.body.template = 'example' this.req.body.template = 'example'
this.res.json = json => { this.res.json = json => {
this.ProjectCreationHandler.createExampleProject this.ProjectCreationHandler.promises.createExampleProject
.calledWith(this.user._id, this.projectName) .calledWith(this.user._id, this.projectName)
.should.equal(true) .should.equal(true)
this.ProjectCreationHandler.createBasicProject.called.should.equal( this.ProjectCreationHandler.promises.createBasicProject.called.should.equal(
false false
) )
done() done()
@ -400,10 +444,10 @@ describe('ProjectController', function () {
it('should call the projectCreationHandler with createBasicProject', function (done) { it('should call the projectCreationHandler with createBasicProject', function (done) {
this.req.body.template = 'basic' this.req.body.template = 'basic'
this.res.json = json => { this.res.json = json => {
this.ProjectCreationHandler.createExampleProject.called.should.equal( this.ProjectCreationHandler.promises.createExampleProject.called.should.equal(
false false
) )
this.ProjectCreationHandler.createBasicProject this.ProjectCreationHandler.promises.createBasicProject
.calledWith(this.user._id, this.projectName) .calledWith(this.user._id, this.projectName)
.should.equal(true) .should.equal(true)
done() done()
@ -419,10 +463,10 @@ describe('ProjectController', function () {
}) })
it('should call the editor controller', function (done) { it('should call the editor controller', function (done) {
this.EditorController.renameProject.callsArgWith(2) this.EditorController.promises.renameProject.resolves()
this.res.sendStatus = code => { this.res.sendStatus = code => {
code.should.equal(200) code.should.equal(200)
this.EditorController.renameProject this.EditorController.promises.renameProject
.calledWith(this.project_id, this.newProjectName) .calledWith(this.project_id, this.newProjectName)
.should.equal(true) .should.equal(true)
done() done()
@ -432,8 +476,7 @@ describe('ProjectController', function () {
it('should send an error to next() if there is a problem', function (done) { it('should send an error to next() if there is a problem', function (done) {
let error let error
this.EditorController.renameProject.callsArgWith( this.EditorController.promises.renameProject.rejects(
2,
(error = new Error('problem')) (error = new Error('problem'))
) )
const next = e => { const next = e => {
@ -470,17 +513,17 @@ describe('ProjectController', function () {
zotero: { encrypted: 'bbbb' }, zotero: { encrypted: 'bbbb' },
}, },
} }
this.ProjectGetter.getProject.callsArgWith(2, null, this.project) this.ProjectGetter.promises.getProject.resolves(this.project)
this.UserModel.findById.callsArgWith(2, null, this.user) this.UserModel.findById.returns({
this.SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, {}) exec: sinon.stub().resolves(this.user),
this.AuthorizationManager.getPrivilegeLevelForProject.callsArgWith( })
3, this.SubscriptionLocator.promises.getUsersSubscription.resolves({})
null, this.AuthorizationManager.promises.getPrivilegeLevelForProject.resolves(
'owner' 'owner'
) )
this.ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub() this.ProjectDeleter.unmarkAsDeletedByExternalSource = sinon.stub()
this.InactiveProjectManager.reactivateProjectIfRequired.callsArgWith(1) this.InactiveProjectManager.promises.reactivateProjectIfRequired.resolves()
this.ProjectUpdateHandler.markAsOpened.callsArgWith(1) this.ProjectUpdateHandler.promises.markAsOpened.resolves()
}) })
it('should render the project/editor page', function (done) { it('should render the project/editor page', function (done) {
@ -526,7 +569,7 @@ describe('ProjectController', function () {
opts.isRestrictedTokenMember.should.equal(false) opts.isRestrictedTokenMember.should.equal(false)
return done() return done()
} }
return this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
}) })
it('should set isRestrictedTokenMember when appropriate', function (done) { it('should set isRestrictedTokenMember when appropriate', function (done) {
@ -536,12 +579,12 @@ describe('ProjectController', function () {
opts.isRestrictedTokenMember.should.equal(true) opts.isRestrictedTokenMember.should.equal(true)
return done() return done()
} }
return this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
}) })
it('should invoke the session maintenance for logged in user', function (done) { it('should invoke the session maintenance for logged in user', function (done) {
this.res.render = () => { this.res.render = () => {
this.SplitTestSessionHandler.sessionMaintenance.should.have.been.calledWith( this.SplitTestSessionHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.req, this.req,
this.user this.user
) )
@ -553,7 +596,7 @@ describe('ProjectController', function () {
it('should invoke the session maintenance for anonymous user', function (done) { it('should invoke the session maintenance for anonymous user', function (done) {
this.SessionManager.getLoggedInUserId.returns(null) this.SessionManager.getLoggedInUserId.returns(null)
this.res.render = () => { this.res.render = () => {
this.SplitTestSessionHandler.sessionMaintenance.should.have.been.calledWith( this.SplitTestSessionHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.req this.req
) )
done() done()
@ -571,11 +614,16 @@ describe('ProjectController', function () {
}) })
it('should not render the page if the project can not be accessed', function (done) { it('should not render the page if the project can not be accessed', function (done) {
this.AuthorizationManager.getPrivilegeLevelForProject = sinon this.AuthorizationManager.promises.getPrivilegeLevelForProject = sinon
.stub() .stub()
.callsArgWith(3, null, null) .resolves(null)
this.res.sendStatus = (resCode, opts) => { this.res.sendStatus = (resCode, opts) => {
resCode.should.equal(401) resCode.should.equal(401)
this.AuthorizationManager.promises.getPrivilegeLevelForProject.should.have.been.calledWith(
this.user._id,
this.project_id,
'some-token'
)
done() done()
} }
this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
@ -583,7 +631,7 @@ describe('ProjectController', function () {
it('should reactivateProjectIfRequired', function (done) { it('should reactivateProjectIfRequired', function (done) {
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
this.InactiveProjectManager.reactivateProjectIfRequired this.InactiveProjectManager.promises.reactivateProjectIfRequired
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true) .should.equal(true)
done() done()
@ -605,7 +653,7 @@ describe('ProjectController', function () {
it('should mark project as opened', function (done) { it('should mark project as opened', function (done) {
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
this.ProjectUpdateHandler.markAsOpened this.ProjectUpdateHandler.promises.markAsOpened
.calledWith(this.project_id) .calledWith(this.project_id)
.should.equal(true) .should.equal(true)
done() done()
@ -614,9 +662,9 @@ describe('ProjectController', function () {
}) })
it('should call the brand variations handler for branded projects', function (done) { it('should call the brand variations handler for branded projects', function (done) {
this.ProjectGetter.getProject.callsArgWith(2, null, this.brandedProject) this.ProjectGetter.promises.getProject.resolves(this.brandedProject)
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
this.BrandVariationsHandler.getBrandVariationById this.BrandVariationsHandler.promises.getBrandVariationById
.calledWith() .calledWith()
.should.equal(true) .should.equal(true)
done() done()
@ -626,7 +674,7 @@ describe('ProjectController', function () {
it('should not call the brand variations handler for unbranded projects', function (done) { it('should not call the brand variations handler for unbranded projects', function (done) {
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
this.BrandVariationsHandler.getBrandVariationById.called.should.equal( this.BrandVariationsHandler.promises.getBrandVariationById.called.should.equal(
false false
) )
done() done()
@ -635,7 +683,7 @@ describe('ProjectController', function () {
}) })
it('should expose the brand variation details as locals for branded projects', function (done) { it('should expose the brand variation details as locals for branded projects', function (done) {
this.ProjectGetter.getProject.callsArgWith(2, null, this.brandedProject) this.ProjectGetter.promises.getProject.resolves(this.brandedProject)
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
opts.brandVariation.should.deep.equal(this.brandVariationDetails) opts.brandVariation.should.deep.equal(this.brandVariationDetails)
done() done()
@ -645,7 +693,7 @@ describe('ProjectController', function () {
it('flushes the project to TPDS if a flush is pending', function (done) { it('flushes the project to TPDS if a flush is pending', function (done) {
this.res.render = () => { this.res.render = () => {
this.TpdsProjectFlusher.flushProjectToTpdsIfNeeded.should.have.been.calledWith( this.TpdsProjectFlusher.promises.flushProjectToTpdsIfNeeded.should.have.been.calledWith(
this.project_id this.project_id
) )
done() done()
@ -656,7 +704,7 @@ describe('ProjectController', function () {
it('should refresh the user features if the epoch is outdated', function (done) { it('should refresh the user features if the epoch is outdated', function (done) {
this.FeaturesUpdater.featuresEpochIsCurrent = sinon.stub().returns(false) this.FeaturesUpdater.featuresEpochIsCurrent = sinon.stub().returns(false)
this.res.render = () => { this.res.render = () => {
this.FeaturesUpdater.refreshFeatures.should.have.been.calledWith( this.FeaturesUpdater.promises.refreshFeatures.should.have.been.calledWith(
this.user._id, this.user._id,
'load-editor' 'load-editor'
) )
@ -898,9 +946,9 @@ describe('ProjectController', function () {
// default to saas enabled // default to saas enabled
this.Features.hasFeature.withArgs('saas').returns(true) this.Features.hasFeature.withArgs('saas').returns(true)
// default to without a subscription // default to without a subscription
this.SubscriptionLocator.getUsersSubscription = sinon this.SubscriptionLocator.promises.getUsersSubscription = sinon
.stub() .stub()
.callsArgWith(1, null, null) .resolves(null)
}) })
it('should not show without the saas feature', function (done) { it('should not show without the saas feature', function (done) {
this.Features.hasFeature.withArgs('saas').returns(false) this.Features.hasFeature.withArgs('saas').returns(false)
@ -918,9 +966,9 @@ describe('ProjectController', function () {
this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
}) })
it('should not show for a user with a personal subscription', function (done) { it('should not show for a user with a personal subscription', function (done) {
this.SubscriptionLocator.getUsersSubscription = sinon this.SubscriptionLocator.promises.getUsersSubscription = sinon
.stub() .stub()
.callsArgWith(1, null, {}) .resolves({})
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
expect(opts.showUpgradePrompt).to.equal(false) expect(opts.showUpgradePrompt).to.equal(false)
done() done()
@ -928,9 +976,9 @@ describe('ProjectController', function () {
this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
}) })
it('should not show for a user who is a member of a group subscription', function (done) { it('should not show for a user who is a member of a group subscription', function (done) {
this.LimitationsManager.userIsMemberOfGroupSubscription = sinon this.LimitationsManager.promises.userIsMemberOfGroupSubscription = sinon
.stub() .stub()
.callsArgWith(1, null, true) .resolves(true)
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
expect(opts.showUpgradePrompt).to.equal(false) expect(opts.showUpgradePrompt).to.equal(false)
done() done()
@ -938,9 +986,9 @@ describe('ProjectController', function () {
this.ProjectController.loadEditor(this.req, this.res) this.ProjectController.loadEditor(this.req, this.res)
}) })
it('should not show for a user with an affiliated paid university', function (done) { it('should not show for a user with an affiliated paid university', function (done) {
this.InstitutionsFeatures.hasLicence = sinon this.InstitutionsFeatures.promises.hasLicence = sinon
.stub() .stub()
.callsArgWith(1, null, true) .resolves(true)
this.res.render = (pageName, opts) => { this.res.render = (pageName, opts) => {
expect(opts.showUpgradePrompt).to.equal(false) expect(opts.showUpgradePrompt).to.equal(false)
done() done()
@ -999,9 +1047,9 @@ describe('ProjectController', function () {
.withArgs(projects[3], this.user._id) .withArgs(projects[3], this.user._id)
.returns(false) .returns(false)
this.ProjectGetter.findAllUsersProjects = sinon this.ProjectGetter.promises.findAllUsersProjects = sinon
.stub() .stub()
.callsArgWith(2, null, []) .resolves([])
this.ProjectController._buildProjectList = sinon.stub().returns(projects) this.ProjectController._buildProjectList = sinon.stub().returns(projects)
this.SessionManager.getLoggedInUserId = sinon this.SessionManager.getLoggedInUserId = sinon
.stub() .stub()
@ -1033,9 +1081,9 @@ describe('ProjectController', function () {
{ path: '/main.tex', doc: true }, { path: '/main.tex', doc: true },
] ]
this.files = [{ path: '/things/a.txt' }] this.files = [{ path: '/things/a.txt' }]
this.ProjectGetter.getProject = sinon this.ProjectGetter.promises.getProject = sinon
.stub() .stub()
.callsArgWith(1, null, this.project) .resolves(this.project)
this.ProjectEntityHandler.getAllEntitiesFromProject = sinon this.ProjectEntityHandler.getAllEntitiesFromProject = sinon
.stub() .stub()
.returns({ docs: this.docs, files: this.files }) .returns({ docs: this.docs, files: this.files })
@ -1051,7 +1099,7 @@ describe('ProjectController', function () {
{ path: '/things/b.txt', type: 'doc' }, { path: '/things/b.txt', type: 'doc' },
], ],
}) })
expect(this.ProjectGetter.getProject.callCount).to.equal(1) expect(this.ProjectGetter.promises.getProject.callCount).to.equal(1)
expect( expect(
this.ProjectEntityHandler.getAllEntitiesFromProject.callCount this.ProjectEntityHandler.getAllEntitiesFromProject.callCount
).to.equal(1) ).to.equal(1)