Просмотр исходного кода

Remove node controller and dead backend code of the old project list (#12222)

GitOrigin-RevId: 31f92b1df484db9e44ea17cd258fdc5fe467a379
M Fahru 3 лет назад
Родитель
Сommit
c1211bef8f

+ 0 - 352
services/web/app/src/Features/Project/ProjectController.js

@@ -11,9 +11,7 @@ const EditorController = require('../Editor/EditorController')
 const ProjectHelper = require('./ProjectHelper')
 const metrics = require('@overleaf/metrics')
 const { User } = require('../../models/User')
-const TagsHandler = require('../Tags/TagsHandler')
 const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
-const NotificationsHandler = require('../Notifications/NotificationsHandler')
 const LimitationsManager = require('../Subscription/LimitationsManager')
 const Settings = require('@overleaf/settings')
 const AuthorizationManager = require('../Authorization/AuthorizationManager')
@@ -28,21 +26,15 @@ const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
 const ProjectEntityHandler = require('./ProjectEntityHandler')
 const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
 const UserGetter = require('../User/UserGetter')
-const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
-const { V1ConnectionError } = require('../Errors/Errors')
 const Features = require('../../infrastructure/Features')
 const BrandVariationsHandler = require('../BrandVariations/BrandVariationsHandler')
 const UserController = require('../User/UserController')
 const AnalyticsManager = require('../Analytics/AnalyticsManager')
-const Modules = require('../../infrastructure/Modules')
 const SplitTestHandler = require('../SplitTests/SplitTestHandler')
 const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
 const SpellingHandler = require('../Spelling/SpellingHandler')
-const UserPrimaryEmailCheckHandler = require('../User/UserPrimaryEmailCheckHandler')
 const { hasAdminAccess } = require('../Helpers/AdminAuthorizationHelper')
 const InstitutionsFeatures = require('../Institutions/InstitutionsFeatures')
-const SubscriptionViewModelBuilder = require('../Subscription/SubscriptionViewModelBuilder')
-const SurveyHandler = require('../Survey/SurveyHandler')
 const ProjectAuditLogHandler = require('./ProjectAuditLogHandler')
 const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
 
@@ -54,25 +46,6 @@ const VISUAL_EDITOR_NAMING_SPLIT_TEST_MIN_SIGNUP_DATE = new Date('2023-04-17')
  * @typedef {import("./types").Project} Project
  */
 
-const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
-  if (!affiliation.institution) return false
-
-  // institution.confirmed is for the domain being confirmed, not the email
-  // Do not show SSO UI for unconfirmed domains
-  if (!affiliation.institution.confirmed) return false
-
-  // Could have multiple emails at the same institution, and if any are
-  // linked to the institution then do not show notification for others
-  if (
-    linkedInstitutionIds.indexOf(affiliation.institution.id.toString()) === -1
-  ) {
-    if (affiliation.institution.ssoEnabled) return true
-    if (affiliation.institution.ssoBeta && session.samlBeta) return true
-    return false
-  }
-  return false
-}
-
 const ProjectController = {
   _isInPercentageRollout(rolloutName, objectId, percentage) {
     if (Settings.bypassPercentageRollouts === true) {
@@ -413,331 +386,6 @@ const ProjectController = {
     })
   },
 
-  projectListPage(req, res, next) {
-    const timer = new metrics.Timer('project-list')
-    const userId = SessionManager.getLoggedInUserId(req.session)
-    const currentUser = SessionManager.getSessionUser(req.session)
-    async.parallel(
-      {
-        tags(cb) {
-          TagsHandler.getAllTags(userId, cb)
-        },
-        notifications(cb) {
-          NotificationsHandler.getUserNotifications(userId, cb)
-        },
-        projects(cb) {
-          ProjectGetter.findAllUsersProjects(
-            userId,
-            'name lastUpdated lastUpdatedBy publicAccesLevel archived trashed owner_ref tokens',
-            cb
-          )
-        },
-        hasSubscription(cb) {
-          LimitationsManager.hasPaidSubscription(
-            currentUser,
-            (error, hasPaidSubscription) => {
-              if (error != null && error instanceof V1ConnectionError) {
-                return cb(null, true)
-              }
-              cb(error, hasPaidSubscription)
-            }
-          )
-        },
-        user(cb) {
-          User.findById(
-            userId,
-            'email emails featureSwitches overleaf awareOfV2 features lastLoginIp lastPrimaryEmailCheck signUpDate',
-            cb
-          )
-        },
-        userEmailsData(cb) {
-          const result = { list: [], allInReconfirmNotificationPeriods: [] }
-
-          UserGetter.getUserFullEmails(userId, (error, fullEmails) => {
-            if (error && error instanceof V1ConnectionError) {
-              return cb(null, result)
-            }
-
-            if (!Features.hasFeature('affiliations')) {
-              result.list = fullEmails
-              return cb(null, result)
-            }
-            Modules.hooks.fire(
-              'allInReconfirmNotificationPeriodsForUser',
-              fullEmails,
-              (error, results) => {
-                if (error != null) {
-                  return cb(error)
-                }
-
-                // Module.hooks.fire accepts multiple methods
-                // and does async.series
-                const allInReconfirmNotificationPeriods =
-                  (results && results[0]) || []
-                cb(null, {
-                  list: fullEmails,
-                  allInReconfirmNotificationPeriods,
-                })
-              }
-            )
-          })
-        },
-        usersBestSubscription(cb) {
-          if (!Features.hasFeature('saas')) {
-            return cb()
-          }
-          SubscriptionViewModelBuilder.getBestSubscription(
-            { _id: userId },
-            (err, subscription) => {
-              if (err) {
-                // do not fail loading the project list when fetching the best subscription fails
-                logger.error(
-                  { userId, err },
-                  'Could not get usersBestSubscription'
-                )
-                return cb(null, { type: 'error' })
-              }
-              cb(null, subscription)
-            }
-          )
-        },
-        userIsMemberOfGroupSubscription(cb) {
-          LimitationsManager.userIsMemberOfGroupSubscription(
-            currentUser,
-            (error, isMember) => {
-              if (error) {
-                logger.error(
-                  { err: error },
-                  'Failed to check whether user is a member of group subscription'
-                )
-                return cb(null, false)
-              }
-              cb(null, isMember)
-            }
-          )
-        },
-        survey(cb) {
-          SurveyHandler.getSurvey(userId, (err, survey) => {
-            if (err) {
-              logger.warn({ err }, 'failed to get survey')
-              // do not fail loading the project list if we fail to load the survey
-              cb(null, null)
-            } else {
-              cb(null, survey)
-            }
-          })
-        },
-      },
-      (err, results) => {
-        if (err != null) {
-          OError.tag(err, 'error getting data for project list page')
-          return next(err)
-        }
-        const {
-          notifications,
-          user,
-          userEmailsData,
-          userIsMemberOfGroupSubscription,
-        } = results
-
-        if (
-          user &&
-          Features.hasFeature('saas') &&
-          UserPrimaryEmailCheckHandler.requiresPrimaryEmailCheck(user)
-        ) {
-          return res.redirect('/user/emails/primary-email-check')
-        }
-
-        const userEmails = userEmailsData.list || []
-
-        const userAffiliations = userEmails
-          .filter(emailData => !!emailData.affiliation)
-          .map(emailData => {
-            const result = emailData.affiliation
-            result.email = emailData.email
-            return result
-          })
-
-        const { allInReconfirmNotificationPeriods } = userEmailsData
-
-        // Handle case of deleted user
-        if (user == null) {
-          UserController.logout(req, res, next)
-          return
-        }
-        const tags = results.tags
-        const notificationsInstitution = []
-        for (const notification of notifications) {
-          notification.html = req.i18n.translate(
-            notification.templateKey,
-            notification.messageOpts
-          )
-        }
-
-        // Institution SSO Notifications
-        let reconfirmedViaSAML
-        if (Features.hasFeature('saml')) {
-          reconfirmedViaSAML = _.get(req.session, ['saml', 'reconfirmed'])
-          const samlSession = req.session.saml
-          // Notification: SSO Available
-          const linkedInstitutionIds = []
-          user.emails.forEach(email => {
-            if (email.samlProviderId) {
-              linkedInstitutionIds.push(email.samlProviderId)
-            }
-          })
-          if (Array.isArray(userAffiliations)) {
-            userAffiliations.forEach(affiliation => {
-              if (
-                _ssoAvailable(affiliation, req.session, linkedInstitutionIds)
-              ) {
-                notificationsInstitution.push({
-                  email: affiliation.email,
-                  institutionId: affiliation.institution.id,
-                  institutionName: affiliation.institution.name,
-                  templateKey: 'notification_institution_sso_available',
-                })
-              }
-            })
-          }
-
-          if (samlSession) {
-            // Notification: After SSO Linked
-            if (samlSession.linked) {
-              notificationsInstitution.push({
-                email: samlSession.institutionEmail,
-                institutionName: samlSession.linked.universityName,
-                templateKey: 'notification_institution_sso_linked',
-              })
-            }
-
-            // Notification: After SSO Linked or Logging in
-            // The requested email does not match primary email returned from
-            // the institution
-            if (
-              samlSession.requestedEmail &&
-              samlSession.emailNonCanonical &&
-              !samlSession.error
-            ) {
-              notificationsInstitution.push({
-                institutionEmail: samlSession.emailNonCanonical,
-                requestedEmail: samlSession.requestedEmail,
-                templateKey: 'notification_institution_sso_non_canonical',
-              })
-            }
-
-            // Notification: Tried to register, but account already existed
-            // registerIntercept is set before the institution callback.
-            // institutionEmail is set after institution callback.
-            // Check for both in case SSO flow was abandoned
-            if (
-              samlSession.registerIntercept &&
-              samlSession.institutionEmail &&
-              !samlSession.error
-            ) {
-              notificationsInstitution.push({
-                email: samlSession.institutionEmail,
-                templateKey: 'notification_institution_sso_already_registered',
-              })
-            }
-
-            // Notification: When there is a session error
-            if (samlSession.error) {
-              notificationsInstitution.push({
-                templateKey: 'notification_institution_sso_error',
-                error: samlSession.error,
-              })
-            }
-          }
-          delete req.session.saml
-        }
-
-        const portalTemplates =
-          ProjectController._buildPortalTemplatesList(userAffiliations)
-        const projects = ProjectController._buildProjectList(
-          results.projects,
-          userId
-        )
-
-        // in v2 add notifications for matching university IPs
-        if (Settings.overleaf != null && req.ip !== user.lastLoginIp) {
-          NotificationsBuilder.ipMatcherAffiliation(user._id).create(
-            req.ip,
-            () => {}
-          )
-        }
-
-        const hasPaidAffiliation = userAffiliations.some(
-          affiliation => affiliation.licence && affiliation.licence !== 'free'
-        )
-
-        const showGroupsAndEnterpriseBanner =
-          Features.hasFeature('saas') &&
-          !userIsMemberOfGroupSubscription &&
-          !hasPaidAffiliation
-
-        const groupsAndEnterpriseBannerVariant =
-          showGroupsAndEnterpriseBanner &&
-          _.sample(['did-you-know', 'on-premise', 'people', 'FOMO'])
-
-        ProjectController._injectProjectUsers(projects, (error, projects) => {
-          if (error != null) {
-            return next(error)
-          }
-          const viewModel = {
-            title: 'your_projects',
-            priority_title: true,
-            projects,
-            tags,
-            notifications: notifications || [],
-            notificationsInstitution,
-            allInReconfirmNotificationPeriods,
-            portalTemplates,
-            user,
-            userAffiliations,
-            userEmails,
-            hasSubscription: results.hasSubscription,
-            reconfirmedViaSAML,
-            zipFileSizeLimit: Settings.maxUploadSize,
-            isOverleaf: !!Settings.overleaf,
-            metadata: { viewport: false },
-            showThinFooter: true, // don't show the fat footer on the projects dashboard, as there's a fixed space available
-            usersBestSubscription: results.usersBestSubscription,
-            survey: results.survey,
-            showGroupsAndEnterpriseBanner,
-            groupsAndEnterpriseBannerVariant,
-          }
-
-          const paidUser =
-            (user.features != null ? user.features.github : undefined) &&
-            (user.features != null ? user.features.dropbox : undefined) // use a heuristic for paid account
-          const freeUserProportion = 0.1
-          const sampleFreeUser =
-            parseInt(user._id.toString().slice(-2), 16) <
-            freeUserProportion * 255
-          const showFrontWidget = paidUser || sampleFreeUser
-
-          if (showFrontWidget) {
-            viewModel.frontChatWidgetRoomId =
-              Settings.overleaf != null
-                ? Settings.overleaf.front_chat_widget_room_id
-                : undefined
-          }
-
-          // null test targeting logged in users
-          SplitTestHandler.promises.getAssignment(
-            req,
-            res,
-            'null-test-dashboard'
-          )
-
-          res.render('project/list', viewModel)
-          timer.done()
-        })
-      }
-    )
-  },
-
   loadEditor(req, res, next) {
     const timer = new metrics.Timer('load-editor')
     if (!Settings.editorIsOpen) {

+ 0 - 544
services/web/test/unit/src/Project/ProjectControllerTests.js

@@ -3,7 +3,6 @@ const path = require('path')
 const sinon = require('sinon')
 const { expect } = require('chai')
 const { ObjectId } = require('mongodb')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
 
 const MODULE_PATH = path.join(
   __dirname,
@@ -62,7 +61,6 @@ describe('ProjectController', function () {
         .callsArgWith(1, null, false),
     }
     this.TagsHandler = { getAllTags: sinon.stub() }
-    this.NotificationsHandler = { getUserNotifications: sinon.stub() }
     this.UserModel = { findById: sinon.stub(), updateOne: sinon.stub() }
     this.AuthorizationManager = {
       getPrivilegeLevelForProject: sinon.stub(),
@@ -71,9 +69,6 @@ describe('ProjectController', function () {
     this.EditorController = { renameProject: sinon.stub() }
     this.InactiveProjectManager = { reactivateProjectIfRequired: sinon.stub() }
     this.ProjectUpdateHandler = { markAsOpened: sinon.stub() }
-    this.UserPrimaryEmailCheckHandler = {
-      requiresPrimaryEmailCheck: sinon.stub().returns(false),
-    }
     this.ProjectGetter = {
       findAllUsersProjects: sinon.stub(),
       getProject: sinon.stub(),
@@ -102,9 +97,6 @@ describe('ProjectController', function () {
       isUserInvitedMemberOfProject: sinon.stub().callsArgWith(2, null, true),
     }
     this.ProjectEntityHandler = {}
-    this.NotificationBuilder = {
-      ipMatcherAffiliation: sinon.stub().returns({ create: sinon.stub() }),
-    }
     this.UserGetter = {
       getUserFullEmails: sinon.stub().yields(null, []),
       getUser: sinon
@@ -166,7 +158,6 @@ describe('ProjectController', function () {
         '../Subscription/SubscriptionLocator': this.SubscriptionLocator,
         '../Subscription/LimitationsManager': this.LimitationsManager,
         '../Tags/TagsHandler': this.TagsHandler,
-        '../Notifications/NotificationsHandler': this.NotificationsHandler,
         '../../models/User': { User: this.UserModel },
         '../Authorization/AuthorizationManager': this.AuthorizationManager,
         '../InactiveData/InactiveProjectManager': this.InactiveProjectManager,
@@ -179,7 +170,6 @@ describe('ProjectController', function () {
         './ProjectEntityHandler': this.ProjectEntityHandler,
         '../../infrastructure/Features': this.Features,
         '../Subscription/FeaturesUpdater': this.FeaturesUpdater,
-        '../Notifications/NotificationsBuilder': this.NotificationBuilder,
         '../User/UserGetter': this.UserGetter,
         '../BrandVariations/BrandVariationsHandler':
           this.BrandVariationsHandler,
@@ -188,16 +178,11 @@ describe('ProjectController', function () {
         '../Analytics/AnalyticsManager': { recordEventForUser: () => {} },
         '../Subscription/SubscriptionViewModelBuilder':
           this.SubscriptionViewModelBuilder,
-        '../../infrastructure/Modules': {
-          hooks: { fire: sinon.stub().yields(null, []) },
-        },
         '../Spelling/SpellingHandler': {
           getUserDictionary: sinon.stub().yields(null, []),
         },
         '../Institutions/InstitutionsFeatures': this.InstitutionsFeatures,
         '../Survey/SurveyHandler': this.SurveyHandler,
-        '../User/UserPrimaryEmailCheckHandler':
-          this.UserPrimaryEmailCheckHandler,
         './ProjectAuditLogHandler': this.ProjectAuditLogHandler,
       },
     })
@@ -404,535 +389,6 @@ describe('ProjectController', function () {
     })
   })
 
-  describe('projectListPage', function () {
-    beforeEach(function () {
-      this.tags = [
-        { name: 1, project_ids: ['1', '2', '3'] },
-        { name: 2, project_ids: ['a', '1'] },
-        { name: 3, project_ids: ['a', 'b', 'c', 'd'] },
-      ]
-      this.notifications = [
-        {
-          _id: '1',
-          user_id: '2',
-          templateKey: '3',
-          messageOpts: '4',
-          key: '5',
-        },
-      ]
-      this.projects = [
-        { _id: 1, lastUpdated: 1, owner_ref: 'user-1' },
-        {
-          _id: 2,
-          lastUpdated: 2,
-          owner_ref: 'user-2',
-          lastUpdatedBy: 'user-1',
-        },
-      ]
-      this.collabertions = [{ _id: 5, lastUpdated: 5, owner_ref: 'user-1' }]
-      this.readOnly = [{ _id: 3, lastUpdated: 3, owner_ref: 'user-1' }]
-      this.tokenReadAndWrite = [{ _id: 6, lastUpdated: 5, owner_ref: 'user-4' }]
-      this.tokenReadOnly = [{ _id: 7, lastUpdated: 4, owner_ref: 'user-5' }]
-      this.allProjects = {
-        owned: this.projects,
-        readAndWrite: this.collabertions,
-        readOnly: this.readOnly,
-        tokenReadAndWrite: this.tokenReadAndWrite,
-        tokenReadOnly: this.tokenReadOnly,
-      }
-
-      this.users = {
-        'user-1': {
-          first_name: 'James',
-        },
-        'user-2': {
-          first_name: 'Henry',
-        },
-      }
-      this.users[this.user._id] = this.user // Owner
-      this.UserModel.findById = (id, fields, callback) => {
-        callback(null, this.users[id])
-      }
-      this.UserGetter.getUser = (id, fields, callback) => {
-        callback(null, this.users[id])
-      }
-
-      this.LimitationsManager.hasPaidSubscription.callsArgWith(1, null, false)
-      this.TagsHandler.getAllTags.callsArgWith(1, null, this.tags)
-      this.NotificationsHandler.getUserNotifications = sinon
-        .stub()
-        .callsArgWith(1, null, this.notifications, {})
-      this.ProjectGetter.findAllUsersProjects.callsArgWith(
-        2,
-        null,
-        this.allProjects
-      )
-    })
-
-    it('should render the project/list page', function (done) {
-      this.res.render = (pageName, opts) => {
-        pageName.should.equal('project/list')
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should send the tags', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.tags.length.should.equal(this.tags.length)
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should create trigger ip matcher notifications', function (done) {
-      this.settings.overleaf = true
-      this.res.render = (pageName, opts) => {
-        this.NotificationBuilder.ipMatcherAffiliation.called.should.equal(true)
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should send the projects', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.projects.length.should.equal(
-          this.projects.length +
-            this.collabertions.length +
-            this.readOnly.length +
-            this.tokenReadAndWrite.length +
-            this.tokenReadOnly.length
-        )
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should send the user', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.user.should.deep.equal(this.user)
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should inject the users', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.projects[0].owner.should.equal(
-          this.users[this.projects[0].owner_ref]
-        )
-        opts.projects[1].owner.should.equal(
-          this.users[this.projects[1].owner_ref]
-        )
-        opts.projects[1].lastUpdatedBy.should.equal(
-          this.users[this.projects[1].lastUpdatedBy]
-        )
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should send hasSubscription == false when no subscription', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.hasSubscription.should.equal(false)
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should send hasSubscription == true when there is a subscription', function (done) {
-      this.LimitationsManager.hasPaidSubscription = sinon
-        .stub()
-        .callsArgWith(1, null, true)
-      this.res.render = (pageName, opts) => {
-        opts.hasSubscription.should.equal(true)
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it("should send the user's best subscription when saas feature present", function (done) {
-      this.Features.hasFeature.withArgs('saas').returns(true)
-      this.res.render = (pageName, opts) => {
-        expect(opts.usersBestSubscription).to.deep.include({ type: 'free' })
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should not return a best subscription without saas feature', function (done) {
-      this.Features.hasFeature.withArgs('saas').returns(false)
-      this.res.render = (pageName, opts) => {
-        expect(opts.usersBestSubscription).to.be.undefined
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    describe('front widget', function (done) {
-      beforeEach(function () {
-        this.settings.overleaf = {
-          front_chat_widget_room_id: 'chat-room-id',
-        }
-      })
-
-      it('should show for paid users', function (done) {
-        this.user.features.github = true
-        this.user.features.dropbox = true
-        this.res.render = (pageName, opts) => {
-          opts.frontChatWidgetRoomId.should.equal(
-            this.settings.overleaf.front_chat_widget_room_id
-          )
-          done()
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      it('should show for sample users', function (done) {
-        this.user._id = ObjectId('588f3ddae8ebc1bac07c9f00') // last two digits
-        this.res.render = (pageName, opts) => {
-          opts.frontChatWidgetRoomId.should.equal(
-            this.settings.overleaf.front_chat_widget_room_id
-          )
-          done()
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      it('should not show for non sample users', function (done) {
-        this.user._id = ObjectId('588f3ddae8ebc1bac07c9fff') // last two digits
-        this.res.render = (pageName, opts) => {
-          expect(opts.frontChatWidgetRoomId).to.equal(undefined)
-          done()
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-    })
-
-    describe('With Institution SSO feature', function () {
-      beforeEach(function (done) {
-        this.institutionEmail = 'test@overleaf.com'
-        this.institutionName = 'Overleaf'
-        this.Features.hasFeature.withArgs('saml').returns(true)
-        this.Features.hasFeature.withArgs('affiliations').returns(true)
-        this.Features.hasFeature.withArgs('overleaf-integration').returns(true)
-        done()
-      })
-      it('should show institution SSO available notification for confirmed domains', function () {
-        this.UserGetter.getUserFullEmails.yields(null, [
-          {
-            email: 'test@overleaf.com',
-            affiliation: {
-              institution: {
-                id: 1,
-                confirmed: true,
-                name: 'Overleaf',
-                ssoBeta: false,
-                ssoEnabled: true,
-              },
-            },
-          },
-        ])
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.include({
-            email: this.institutionEmail,
-            institutionId: 1,
-            institutionName: this.institutionName,
-            templateKey: 'notification_institution_sso_available',
-          })
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-      it('should show a linked notification', function () {
-        this.req.session.saml = {
-          institutionEmail: this.institutionEmail,
-          linked: {
-            hasEntitlement: false,
-            universityName: this.institutionName,
-          },
-        }
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.include({
-            email: this.institutionEmail,
-            institutionName: this.institutionName,
-            templateKey: 'notification_institution_sso_linked',
-          })
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-      it('should show a linked another email notification', function () {
-        // when they request to link an email but the institution returns
-        // a different email
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.include({
-            institutionEmail: this.institutionEmail,
-            requestedEmail: 'requested@overleaf.com',
-            templateKey: 'notification_institution_sso_non_canonical',
-          })
-        }
-        this.req.session.saml = {
-          emailNonCanonical: this.institutionEmail,
-          institutionEmail: this.institutionEmail,
-          requestedEmail: 'requested@overleaf.com',
-          linked: {
-            hasEntitlement: false,
-            universityName: this.institutionName,
-          },
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      it('should show a notification when intent was to register via SSO but account existed', function () {
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.include({
-            email: this.institutionEmail,
-            templateKey: 'notification_institution_sso_already_registered',
-          })
-        }
-        this.req.session.saml = {
-          institutionEmail: this.institutionEmail,
-          linked: {
-            hasEntitlement: false,
-            universityName: 'Overleaf',
-          },
-          registerIntercept: {
-            id: 1,
-            name: 'Example University',
-          },
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      it('should not show a register notification if the flow was abandoned', function () {
-        // could initially start to register with an SSO email and then
-        // abandon flow and login with an existing non-institution SSO email
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.not.include({
-            email: 'test@overleaf.com',
-            templateKey: 'notification_institution_sso_already_registered',
-          })
-        }
-        this.req.session.saml = {
-          registerIntercept: {
-            id: 1,
-            name: 'Example University',
-          },
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      it('should show error notification', function () {
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution.length).to.equal(1)
-          expect(opts.notificationsInstitution[0].templateKey).to.equal(
-            'notification_institution_sso_error'
-          )
-          expect(opts.notificationsInstitution[0].error).to.be.instanceof(
-            Errors.SAMLAlreadyLinkedError
-          )
-        }
-        this.req.session.saml = {
-          institutionEmail: this.institutionEmail,
-          error: new Errors.SAMLAlreadyLinkedError(),
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-
-      describe('for an unconfirmed domain for an SSO institution', function () {
-        beforeEach(function (done) {
-          this.UserGetter.getUserFullEmails.yields(null, [
-            {
-              email: 'test@overleaf-uncofirmed.com',
-              affiliation: {
-                institution: {
-                  id: 1,
-                  confirmed: false,
-                  name: 'Overleaf',
-                  ssoBeta: false,
-                  ssoEnabled: true,
-                },
-              },
-            },
-          ])
-          done()
-        })
-        it('should not show institution SSO available notification', function () {
-          this.res.render = (pageName, opts) => {
-            expect(opts.notificationsInstitution.length).to.equal(0)
-          }
-          this.ProjectController.projectListPage(this.req, this.res)
-        })
-      })
-      describe('when linking/logging in initiated on institution side', function () {
-        it('should not show a linked another email notification', function () {
-          // this is only used when initated on Overleaf,
-          // because we keep track of the requested email they tried to link
-          this.res.render = (pageName, opts) => {
-            expect(opts.notificationsInstitution).to.not.deep.include({
-              institutionEmail: this.institutionEmail,
-              requestedEmail: undefined,
-              templateKey: 'notification_institution_sso_non_canonical',
-            })
-          }
-          this.req.session.saml = {
-            emailNonCanonical: this.institutionEmail,
-            institutionEmail: this.institutionEmail,
-            linked: {
-              hasEntitlement: false,
-              universityName: this.institutionName,
-            },
-          }
-          this.ProjectController.projectListPage(this.req, this.res)
-        })
-      })
-      describe('Institution with SSO beta testable', function () {
-        beforeEach(function (done) {
-          this.UserGetter.getUserFullEmails.yields(null, [
-            {
-              email: 'beta@beta.com',
-              affiliation: {
-                institution: {
-                  id: 2,
-                  confirmed: true,
-                  name: 'Beta University',
-                  ssoBeta: true,
-                  ssoEnabled: false,
-                },
-              },
-            },
-          ])
-          done()
-        })
-        it('should show institution SSO available notification when on a beta testing session', function () {
-          this.req.session.samlBeta = true
-          this.res.render = (pageName, opts) => {
-            expect(opts.notificationsInstitution).to.deep.include({
-              email: 'beta@beta.com',
-              institutionId: 2,
-              institutionName: 'Beta University',
-              templateKey: 'notification_institution_sso_available',
-            })
-          }
-          this.ProjectController.projectListPage(this.req, this.res)
-        })
-        it('should not show institution SSO available notification when not on a beta testing session', function () {
-          this.req.session.samlBeta = false
-          this.res.render = (pageName, opts) => {
-            expect(opts.notificationsInstitution).to.deep.not.include({
-              email: 'test@overleaf.com',
-              institutionId: 1,
-              institutionName: 'Overleaf',
-              templateKey: 'notification_institution_sso_available',
-            })
-          }
-          this.ProjectController.projectListPage(this.req, this.res)
-        })
-      })
-    })
-
-    describe('Without Institution SSO feature', function () {
-      beforeEach(function (done) {
-        this.Features.hasFeature.withArgs('saml').returns(false)
-        done()
-      })
-      it('should not show institution sso available notification', function () {
-        this.res.render = (pageName, opts) => {
-          expect(opts.notificationsInstitution).to.deep.not.include({
-            email: 'test@overleaf.com',
-            institutionId: 1,
-            institutionName: 'Overleaf',
-            templateKey: 'notification_institution_sso_available',
-          })
-        }
-        this.ProjectController.projectListPage(this.req, this.res)
-      })
-    })
-  })
-
-  describe('projectListPage with duplicate projects', function () {
-    beforeEach(function () {
-      this.tags = [
-        { name: 1, project_ids: ['1', '2', '3'] },
-        { name: 2, project_ids: ['a', '1'] },
-        { name: 3, project_ids: ['a', 'b', 'c', 'd'] },
-      ]
-      this.notifications = [
-        {
-          _id: '1',
-          user_id: '2',
-          templateKey: '3',
-          messageOpts: '4',
-          key: '5',
-        },
-      ]
-      this.projects = [
-        { _id: 1, lastUpdated: 1, owner_ref: 'user-1' },
-        { _id: 2, lastUpdated: 2, owner_ref: 'user-2' },
-      ]
-      this.collabertions = [{ _id: 5, lastUpdated: 5, owner_ref: 'user-1' }]
-      this.readOnly = [{ _id: 3, lastUpdated: 3, owner_ref: 'user-1' }]
-      this.tokenReadAndWrite = [{ _id: 6, lastUpdated: 5, owner_ref: 'user-4' }]
-      this.tokenReadOnly = [
-        { _id: 6, lastUpdated: 5, owner_ref: 'user-4' }, // Also in tokenReadAndWrite
-        { _id: 7, lastUpdated: 4, owner_ref: 'user-5' },
-      ]
-      this.allProjects = {
-        owned: this.projects,
-        readAndWrite: this.collabertions,
-        readOnly: this.readOnly,
-        tokenReadAndWrite: this.tokenReadAndWrite,
-        tokenReadOnly: this.tokenReadOnly,
-      }
-
-      this.users = {
-        'user-1': {
-          first_name: 'James',
-        },
-        'user-2': {
-          first_name: 'Henry',
-        },
-      }
-      this.users[this.user._id] = this.user // Owner
-      this.UserModel.findById = (id, fields, callback) => {
-        callback(null, this.users[id])
-      }
-
-      this.LimitationsManager.hasPaidSubscription.callsArgWith(1, null, false)
-      this.TagsHandler.getAllTags.callsArgWith(1, null, this.tags)
-      this.NotificationsHandler.getUserNotifications = sinon
-        .stub()
-        .callsArgWith(1, null, this.notifications, {})
-      this.ProjectGetter.findAllUsersProjects.callsArgWith(
-        2,
-        null,
-        this.allProjects
-      )
-    })
-
-    it('should render the project/list page', function (done) {
-      this.res.render = (pageName, opts) => {
-        pageName.should.equal('project/list')
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-
-    it('should omit one of the projects', function (done) {
-      this.res.render = (pageName, opts) => {
-        opts.projects.length.should.equal(
-          this.projects.length +
-            this.collabertions.length +
-            this.readOnly.length +
-            this.tokenReadAndWrite.length +
-            this.tokenReadOnly.length -
-            1
-        )
-        done()
-      }
-      this.ProjectController.projectListPage(this.req, this.res)
-    })
-  })
-
   describe('renameProject', function () {
     beforeEach(function () {
       this.newProjectName = 'my supper great new project'