From fe4c48b7fb527be3e966f8c31433a35279bd4c98 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 Jul 2021 11:26:23 +0200 Subject: [PATCH] Merge pull request #4325 from overleaf/jpa-core-tests-in-saas-ce-pro [misc] run core tests in SAAS/Server CE/Server Pro environment GitOrigin-RevId: 6278ae1eb760a4c0c16da1b71efdde844764a526 --- services/web/Makefile | 37 +- services/web/Makefile.module | 7 +- .../Project/ProjectCreationHandler.js | 7 +- .../src/Features/Project/ProjectDeleter.js | 11 +- .../web/app/src/infrastructure/Features.js | 21 +- services/web/app/src/router.js | 2 +- services/web/app/views/layout/navbar.pug | 2 +- .../config/settings.test.defaults.js | 196 +++ .../acceptance/config/settings.test.saas.js | 249 +--- .../config/settings.test.server-ce.js | 7 +- .../config/settings.test.server-pro.js | 14 +- .../web/test/acceptance/src/BonusTests.js | 28 - .../web/test/acceptance/src/CloseSiteTests.js | 2 +- .../web/test/acceptance/src/DeletionTests.js | 171 --- .../web/test/acceptance/src/ExportsTests.js | 111 -- .../acceptance/src/FeatureUpdaterTests.js | 395 ------ .../web/test/acceptance/src/HistoryTests.js | 227 ---- services/web/test/acceptance/src/Init.js | 14 +- .../web/test/acceptance/src/LabelsTests.js | 128 -- .../test/acceptance/src/ProjectInviteTests.js | 4 +- .../acceptance/src/ProjectStructureTests.js | 1005 -------------- services/web/test/acceptance/src/ProxyUrls.js | 76 -- .../src/RecurlySubscriptionUpdateTests.js | 43 - .../test/acceptance/src/RegistrationTests.js | 42 +- .../test/acceptance/src/RestoringFilesTest.js | 247 +--- .../web/test/acceptance/src/SettingsTests.js | 42 - .../src/SubscriptionDashboardTests.js | 516 ------- .../src/SubscriptionDeletionTests.js | 112 -- .../src/SubscriptionFeaturesTests.js | 74 - .../test/acceptance/src/TokenAccessTests.js | 268 +--- .../test/acceptance/src/UserEmailsTests.js | 1188 ----------------- .../test/acceptance/src/UserHelperTests.js | 4 +- .../src/UserThirdPartyIdentityTests.js | 229 ---- .../acceptance/src/helpers/MongoHelper.js | 2 - .../unit/src/Project/ProjectDeleterTests.js | 51 + .../unit/src/infrastructure/FeaturesTests.js | 7 +- 36 files changed, 363 insertions(+), 5176 deletions(-) create mode 100644 services/web/test/acceptance/config/settings.test.defaults.js delete mode 100644 services/web/test/acceptance/src/BonusTests.js delete mode 100644 services/web/test/acceptance/src/ExportsTests.js delete mode 100644 services/web/test/acceptance/src/FeatureUpdaterTests.js delete mode 100644 services/web/test/acceptance/src/HistoryTests.js delete mode 100644 services/web/test/acceptance/src/LabelsTests.js delete mode 100644 services/web/test/acceptance/src/ProxyUrls.js delete mode 100644 services/web/test/acceptance/src/RecurlySubscriptionUpdateTests.js delete mode 100644 services/web/test/acceptance/src/SubscriptionDashboardTests.js delete mode 100644 services/web/test/acceptance/src/SubscriptionDeletionTests.js delete mode 100644 services/web/test/acceptance/src/SubscriptionFeaturesTests.js delete mode 100644 services/web/test/acceptance/src/UserEmailsTests.js delete mode 100644 services/web/test/acceptance/src/UserThirdPartyIdentityTests.js diff --git a/services/web/Makefile b/services/web/Makefile index e5baae08c7..e0c6c13eaf 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -129,11 +129,27 @@ test_frontend: # test_acceptance: test_acceptance_app test_acceptance_modules +test_acceptance_saas: test_acceptance_app_saas test_acceptance_modules_merged_saas +test_acceptance_server_ce: test_acceptance_app_server_ce test_acceptance_modules_merged_server_ce +test_acceptance_server_pro: test_acceptance_app_server_pro test_acceptance_modules_merged_server_pro -test_acceptance_app: - COMPOSE_PROJECT_NAME=acceptance_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0 - COMPOSE_PROJECT_NAME=acceptance_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run --rm test_acceptance - COMPOSE_PROJECT_NAME=acceptance_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0 +TEST_ACCEPTANCE_APP := \ + test_acceptance_app_saas \ + test_acceptance_app_server_ce \ + test_acceptance_app_server_pro \ + +test_acceptance_app: $(TEST_ACCEPTANCE_APP) +test_acceptance_app_saas: export COMPOSE_PROJECT_NAME=acceptance_test_saas_$(BUILD_DIR_NAME) +test_acceptance_app_saas: export SHARELATEX_CONFIG=$(CFG_SAAS) +test_acceptance_app_server_ce: export COMPOSE_PROJECT_NAME=acceptance_test_server_ce_$(BUILD_DIR_NAME) +test_acceptance_app_server_ce: export SHARELATEX_CONFIG=$(CFG_SERVER_CE) +test_acceptance_app_server_pro: export COMPOSE_PROJECT_NAME=acceptance_test_server_pro_$(BUILD_DIR_NAME) +test_acceptance_app_server_pro: export SHARELATEX_CONFIG=$(CFG_SERVER_PRO) + +$(TEST_ACCEPTANCE_APP): + $(DOCKER_COMPOSE) down -v -t 0 + $(DOCKER_COMPOSE) run --rm test_acceptance + $(DOCKER_COMPOSE) down -v -t 0 # We are using _make magic_ for turning these file-targets into calls to # sub-Makefiles in the individual modules. @@ -260,19 +276,6 @@ $(TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS): test_acceptance_modules: $(TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS) -test_acceptance_app_merged_inner: - npm run --silent test:acceptance:app - -test_acceptance_merged_inner: test_acceptance_app_merged_inner -test_acceptance_merged_inner: test_acceptance_modules_merged_inner - -test_acceptance_merged: export COMPOSE_PROJECT_NAME = \ - acceptance_test_merged_$(BUILD_DIR_NAME) -test_acceptance_merged: - $(DOCKER_COMPOSE) down -v -t 0 - $(DOCKER_COMPOSE) run --rm test_acceptance make test_acceptance_merged_inner - $(DOCKER_COMPOSE) down -v -t 0 - # # CI tests # diff --git a/services/web/Makefile.module b/services/web/Makefile.module index 37915e9426..d4951cf82e 100644 --- a/services/web/Makefile.module +++ b/services/web/Makefile.module @@ -44,8 +44,11 @@ ALL_TEST_ACCEPTANCE_VARIANTS := \ test_acceptance_server_pro \ ifeq (,$(wildcard test/acceptance)) -$(ALL_TEST_ACCEPTANCE_VARIANTS): -test_acceptance_merged_inner: +$(ALL_TEST_ACCEPTANCE_VARIANTS) test_acceptance_merged_inner: + @echo + @echo Module $(MODULE_NAME) does not have acceptance tests. + @echo + clean_test_acceptance: else diff --git a/services/web/app/src/Features/Project/ProjectCreationHandler.js b/services/web/app/src/Features/Project/ProjectCreationHandler.js index 3a0887fa26..f509ae2dbf 100644 --- a/services/web/app/src/Features/Project/ProjectCreationHandler.js +++ b/services/web/app/src/Features/Project/ProjectCreationHandler.js @@ -3,6 +3,7 @@ const OError = require('@overleaf/o-error') const metrics = require('@overleaf/metrics') const Settings = require('@overleaf/settings') const { ObjectId } = require('mongodb') +const Features = require('../../infrastructure/Features') const { Project } = require('../../models/Project') const { Folder } = require('../../models/Folder') const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler') @@ -184,7 +185,11 @@ async function _createBlankProject(ownerId, projectName, attributes = {}) { // only display full project history when the project has the overleaf history id attribute // (to allow scripted creation of projects without full project history) const historyId = _.get(attributes, ['overleaf', 'history', 'id']) - if (Settings.apis.project_history.displayHistoryForNewProjects && historyId) { + if ( + Features.hasFeature('history-v1') && + Settings.apis.project_history.displayHistoryForNewProjects && + historyId + ) { project.overleaf.history.display = true } if (Settings.currentImageName) { diff --git a/services/web/app/src/Features/Project/ProjectDeleter.js b/services/web/app/src/Features/Project/ProjectDeleter.js index c388d1d6a7..0800fa6ab9 100644 --- a/services/web/app/src/Features/Project/ProjectDeleter.js +++ b/services/web/app/src/Features/Project/ProjectDeleter.js @@ -1,3 +1,4 @@ +const Features = require('../../infrastructure/Features') const _ = require('lodash') const { db, ObjectId } = require('../../infrastructure/mongodb') const { callbackify } = require('util') @@ -375,10 +376,12 @@ async function expireDeletedProject(projectId) { await Promise.all([ DocstoreManager.promises.destroyProject(deletedProject.project._id), - HistoryManager.promises.deleteProject( - deletedProject.project._id, - historyId - ), + Features.hasFeature('history-v1') + ? HistoryManager.promises.deleteProject( + deletedProject.project._id, + historyId + ) + : Promise.resolve(), FilestoreHandler.promises.deleteProject(deletedProject.project._id), TpdsUpdateSender.promises.deleteProject({ project_id: deletedProject.project._id, diff --git a/services/web/app/src/infrastructure/Features.js b/services/web/app/src/infrastructure/Features.js index 571646c8b5..a4d9d016b9 100644 --- a/services/web/app/src/infrastructure/Features.js +++ b/services/web/app/src/infrastructure/Features.js @@ -1,17 +1,18 @@ const _ = require('lodash') const Settings = require('@overleaf/settings') -const fs = require('fs') -const publicRegistrationModuleAvailable = fs.existsSync( - `${__dirname}/../../../modules/public-registration` +const publicRegistrationModuleAvailable = Settings.moduleImportSequence.includes( + 'public-registration' ) -const supportModuleAvailable = fs.existsSync( - `${__dirname}/../../../modules/support` +const supportModuleAvailable = Settings.moduleImportSequence.includes('support') + +const historyV1ModuleAvailable = Settings.moduleImportSequence.includes( + 'history-v1' ) -const trackChangesModuleAvailable = fs.existsSync( - `${__dirname}/../../../modules/track-changes` +const trackChangesModuleAvailable = Settings.moduleImportSequence.includes( + 'track-changes' ) /** @@ -56,11 +57,13 @@ const Features = { return Boolean(Settings.overleaf) case 'homepage': return Boolean(Settings.enableHomepage) - case 'registration': + case 'registration-page': return ( !Features.externalAuthenticationSystemUsed() || Boolean(Settings.overleaf) ) + case 'registration': + return publicRegistrationModuleAvailable || Boolean(Settings.overleaf) case 'github-sync': return Boolean(Settings.enableGithubSync) case 'git-bridge': @@ -71,6 +74,8 @@ const Features = { return Boolean(Settings.oauth) case 'templates-server-pro': return !Settings.overleaf + case 'history-v1': + return historyV1ModuleAvailable case 'affiliations': case 'analytics': return Boolean(_.get(Settings, ['apis', 'v1', 'url'])) diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js index 4bd030e3fd..0e5657c85e 100644 --- a/services/web/app/src/router.js +++ b/services/web/app/src/router.js @@ -91,7 +91,7 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) { webRouter.get('/restricted', AuthorizationMiddleware.restricted) - if (Features.hasFeature('registration')) { + if (Features.hasFeature('registration-page')) { webRouter.get('/register', UserPagesController.registerPage) AuthenticationController.addEndpointToLoginWhitelist('/register') } diff --git a/services/web/app/views/layout/navbar.pug b/services/web/app/views/layout/navbar.pug index 62ccd27c16..67215f06b3 100644 --- a/services/web/app/views/layout/navbar.pug +++ b/services/web/app/views/layout/navbar.pug @@ -64,7 +64,7 @@ nav.navbar.navbar-default.navbar-main // logged out if !getSessionUser() // register link - if hasFeature('registration') + if hasFeature('registration-page') li a(href="/register") #{translate('register')} diff --git a/services/web/test/acceptance/config/settings.test.defaults.js b/services/web/test/acceptance/config/settings.test.defaults.js new file mode 100644 index 0000000000..a375b1b179 --- /dev/null +++ b/services/web/test/acceptance/config/settings.test.defaults.js @@ -0,0 +1,196 @@ +const { merge } = require('@overleaf/settings/merge') + +let features + +const httpAuthUser = 'sharelatex' +const httpAuthPass = 'password' +const httpAuthUsers = {} +httpAuthUsers[httpAuthUser] = httpAuthPass + +module.exports = { + catchErrors: false, + clsiCookie: undefined, + + cacheStaticAssets: true, + + httpAuthUsers, + secureCookie: false, + security: { + sessionSecret: 'static-secret-for-tests', + }, + adminDomains: ['example.com'], + + statusPageUrl: 'status.example.com', + + apis: { + linkedUrlProxy: { + url: process.env.LINKED_URL_PROXY, + }, + + web: { + user: httpAuthUser, + pass: httpAuthPass, + }, + }, + + // for registration via SL, set enableLegacyRegistration to true + // for registration via Overleaf v1, set enableLegacyLogin to true + + // Currently, acceptance tests require enableLegacyRegistration. + enableLegacyRegistration: true, + + features: (features = { + v1_free: { + collaborators: 1, + dropbox: false, + versioning: false, + github: true, + gitBridge: true, + templates: false, + references: false, + referencesSearch: false, + mendeley: true, + zotero: true, + compileTimeout: 60, + compileGroup: 'standard', + trackChanges: false, + }, + personal: { + collaborators: 1, + dropbox: false, + versioning: false, + github: false, + gitBridge: false, + templates: false, + references: false, + referencesSearch: false, + mendeley: false, + zotero: false, + compileTimeout: 60, + compileGroup: 'standard', + trackChanges: false, + }, + collaborator: { + collaborators: 10, + dropbox: true, + versioning: true, + github: true, + gitBridge: true, + templates: true, + references: true, + referencesSearch: true, + mendeley: true, + zotero: true, + compileTimeout: 180, + compileGroup: 'priority', + trackChanges: true, + }, + professional: { + collaborators: -1, + dropbox: true, + versioning: true, + github: true, + gitBridge: true, + templates: true, + references: true, + referencesSearch: true, + mendeley: true, + zotero: true, + compileTimeout: 180, + compileGroup: 'priority', + trackChanges: true, + }, + }), + + defaultFeatures: features.personal, + defaultPlanCode: 'personal', + institutionPlanCode: 'professional', + + plans: [ + { + planCode: 'v1_free', + name: 'V1 Free', + price: 0, + features: features.v1_free, + }, + { + planCode: 'personal', + name: 'Personal', + price: 0, + features: features.personal, + }, + { + planCode: 'collaborator', + name: 'Collaborator', + price: 1500, + features: features.collaborator, + }, + { + planCode: 'professional', + name: 'Professional', + price: 3000, + features: features.professional, + }, + ], + + bonus_features: { + 1: { + collaborators: 2, + dropbox: false, + versioning: false, + }, + 3: { + collaborators: 4, + dropbox: false, + versioning: false, + }, + 6: { + collaborators: 4, + dropbox: true, + versioning: true, + }, + 9: { + collaborators: -1, + dropbox: true, + versioning: true, + }, + }, + + redirects: { + '/redirect/one': '/destination/one', + '/redirect/get_and_post': { + methods: ['get', 'post'], + url: '/destination/get_and_post', + }, + '/redirect/base_url': { + baseUrl: 'https://example.com', + url: '/destination/base_url', + }, + '/redirect/params/:id': { + url(params) { + return `/destination/${params.id}/params` + }, + }, + '/redirect/qs': '/destination/qs', + '/docs_v1': { + url: '/docs', + }, + }, + + reconfirmNotificationDays: 14, + + unsupportedBrowsers: { + ie: '<=11', + }, + + // No email in tests + email: undefined, + + test: { + counterInit: 0, + }, +} + +module.exports.mergeWith = function (overrides) { + return merge(overrides, module.exports) +} diff --git a/services/web/test/acceptance/config/settings.test.saas.js b/services/web/test/acceptance/config/settings.test.saas.js index a18cffb461..61657ffb25 100644 --- a/services/web/test/acceptance/config/settings.test.saas.js +++ b/services/web/test/acceptance/config/settings.test.saas.js @@ -1,215 +1,46 @@ -const base = require('../../../config/settings.overrides.saas') - -let features -const v1Api = { - url: 'http://localhost:5000', - user: 'overleaf', - pass: 'password', -} +const { merge } = require('@overleaf/settings/merge') +const baseApp = require('../../../config/settings.overrides.saas') +const baseTest = require('./settings.test.defaults') const httpAuthUser = 'sharelatex' const httpAuthPass = 'password' const httpAuthUsers = {} httpAuthUsers[httpAuthUser] = httpAuthPass -module.exports = base.mergeWith({ - catchErrors: false, - clsiCookie: undefined, - - cacheStaticAssets: true, +const overrides = { enableSubscriptions: true, - httpAuthUsers, - secureCookie: false, - security: { - sessionSecret: 'static-secret-for-tests', - }, - adminDomains: ['example.com'], - apis: { - web: { - user: httpAuthUser, - pass: httpAuthPass, - }, - v1: { - url: v1Api.url, - user: v1Api.user, - pass: v1Api.pass, + project_history: { + sendProjectStructureOps: true, + initializeHistoryForNewProjects: true, + displayHistoryForNewProjects: true, + url: `http://localhost:3054`, }, + recurly: { - // Set up our own mock recurly server url: 'http://localhost:6034', subdomain: 'test', apiKey: 'private-nonsense', webhookUser: 'recurly', webhookPass: 'webhook', }, + tpdsworker: { // Disable tpdsworker in CI. url: undefined, }, - }, - // for registration via SL, set enableLegacyRegistration to true - // for registration via Overleaf v1, set enableLegacyLogin to true + v1: { + url: 'http://localhost:5000', + user: 'overleaf', + pass: 'password', + }, - // Currently, acceptance tests require enableLegacyRegistration. - enableLegacyRegistration: true, - - features: (features = { - v1_free: { - collaborators: 1, - dropbox: false, - versioning: false, - github: true, - gitBridge: true, - templates: false, - references: false, - referencesSearch: false, - mendeley: true, - zotero: true, - compileTimeout: 60, - compileGroup: 'standard', - trackChanges: false, - }, - personal: { - collaborators: 1, - dropbox: false, - versioning: false, - github: false, - gitBridge: false, - templates: false, - references: false, - referencesSearch: false, - mendeley: false, - zotero: false, - compileTimeout: 60, - compileGroup: 'standard', - trackChanges: false, - }, - collaborator: { - collaborators: 10, - dropbox: true, - versioning: true, - github: true, - gitBridge: true, - templates: true, - references: true, - referencesSearch: true, - mendeley: true, - zotero: true, - compileTimeout: 180, - compileGroup: 'priority', - trackChanges: true, - }, - professional: { - collaborators: -1, - dropbox: true, - versioning: true, - github: true, - gitBridge: true, - templates: true, - references: true, - referencesSearch: true, - mendeley: true, - zotero: true, - compileTimeout: 180, - compileGroup: 'priority', - trackChanges: true, - }, - }), - - defaultFeatures: features.personal, - defaultPlanCode: 'personal', - institutionPlanCode: 'professional', - - plans: [ - { - planCode: 'v1_free', - name: 'V1 Free', - price: 0, - features: features.v1_free, - }, - { - planCode: 'personal', - name: 'Personal', - price: 0, - features: features.personal, - }, - { - planCode: 'collaborator', - name: 'Collaborator', - price: 1500, - features: features.collaborator, - }, - { - planCode: 'professional', - name: 'Professional', - price: 3000, - features: features.professional, - }, - ], - - bonus_features: { - 1: { - collaborators: 2, - dropbox: false, - versioning: false, - }, - 3: { - collaborators: 4, - dropbox: false, - versioning: false, - }, - 6: { - collaborators: 4, - dropbox: true, - versioning: true, - }, - 9: { - collaborators: -1, - dropbox: true, - versioning: true, - }, - }, - - proxyUrls: { - '/institutions/list': { baseUrl: v1Api.url, path: '/universities/list' }, - '/institutions/list/:id': { - baseUrl: v1Api.url, - path(params) { - return `/universities/list/${params.id}` - }, - }, - '/institutions/domains': { - baseUrl: v1Api.url, - path: '/university/domains', - }, - '/proxy/missing/baseUrl': { path: '/foo/bar' }, - '/proxy/get_and_post': { - methods: ['get', 'post'], - path: '/destination/get_and_post', - }, - }, - - redirects: { - '/redirect/one': '/destination/one', - '/redirect/get_and_post': { - methods: ['get', 'post'], - url: '/destination/get_and_post', - }, - '/redirect/base_url': { - baseUrl: 'https://example.com', - url: '/destination/base_url', - }, - '/redirect/params/:id': { - url(params) { - return `/destination/${params.id}/params` - }, - }, - '/redirect/qs': '/destination/qs', - '/docs_v1': { - url: '/docs', + v1_history: { + url: `http://localhost:3100/api`, + user: 'overleaf', + pass: 'password', }, }, @@ -225,41 +56,21 @@ module.exports = base.mergeWith({ }, }, - // for testing /user/bonus - social: { - twitter: { - handle: 'overleaf', - }, - - facebook: { - appId: '400474170024644', - picture: 'https://www.overleaf.com/img/ol-brand/logo-horizontal.png', - redirectUri: 'https://www.overleaf.com', - }, - }, - overleaf: { oauth: undefined, }, saml: undefined, - reconfirmNotificationDays: 14, - - unsupportedBrowsers: { - ie: '<=11', - }, - // Disable contentful module. contentful: undefined, - - // No email in tests - email: undefined, - - test: { - counterInit: 0, - }, -}) - -for (const redisKey of Object.keys(base.redis)) { - base.redis[redisKey].host = process.env.REDIS_HOST || 'localhost' +} + +module.exports = baseApp.mergeWith(baseTest.mergeWith(overrides)) + +for (const redisKey of Object.keys(module.exports.redis)) { + module.exports.redis[redisKey].host = process.env.REDIS_HOST || 'localhost' +} + +module.exports.mergeWith = function (overrides) { + return merge(overrides, module.exports) } diff --git a/services/web/test/acceptance/config/settings.test.server-ce.js b/services/web/test/acceptance/config/settings.test.server-ce.js index 9f309ab61a..e67d09eb05 100644 --- a/services/web/test/acceptance/config/settings.test.server-ce.js +++ b/services/web/test/acceptance/config/settings.test.server-ce.js @@ -1,10 +1,7 @@ const { merge } = require('@overleaf/settings/merge') +const base = require('./settings.test.defaults') -module.exports = { - test: { - counterInit: 0, - }, -} +module.exports = base.mergeWith({}) module.exports.mergeWith = function (overrides) { return merge(overrides, module.exports) diff --git a/services/web/test/acceptance/config/settings.test.server-pro.js b/services/web/test/acceptance/config/settings.test.server-pro.js index 9ab298ae2d..f0424656c8 100644 --- a/services/web/test/acceptance/config/settings.test.server-pro.js +++ b/services/web/test/acceptance/config/settings.test.server-pro.js @@ -1,7 +1,9 @@ -const base = require('../../../config/settings.overrides.server-pro') +const { merge } = require('@overleaf/settings/merge') +const baseApp = require('../../../config/settings.overrides.server-pro') +const baseTest = require('./settings.test.defaults') -module.exports = base.mergeWith({ - test: { - counterInit: 0, - }, -}) +module.exports = baseApp.mergeWith(baseTest.mergeWith({})) + +module.exports.mergeWith = function (overrides) { + return merge(overrides, module.exports) +} diff --git a/services/web/test/acceptance/src/BonusTests.js b/services/web/test/acceptance/src/BonusTests.js deleted file mode 100644 index 4dd4382a3c..0000000000 --- a/services/web/test/acceptance/src/BonusTests.js +++ /dev/null @@ -1,28 +0,0 @@ -const { expect } = require('chai') -const cheerio = require('cheerio') -const UserHelper = require('../src/helpers/UserHelper') - -describe('Bonus', function () { - let userHelper - beforeEach(async function () { - userHelper = new UserHelper() - const email = userHelper.getDefaultEmail() - userHelper = await UserHelper.createUser({ email }) - userHelper = await UserHelper.loginUser({ - email, - password: userHelper.getDefaultPassword(), - }) - }) - - it('should use the count rather than refered_users', async function () { - await UserHelper.updateUser(userHelper.user._id, { - $set: { refered_user_count: 1, refered_users: [] }, - }) - - const response = await userHelper.request.get('/user/bonus') - expect(response.statusCode).to.equal(200) - - const dom = cheerio.load(response.body) - expect(dom('.bonus-status').text()).to.match(/You've introduced 1 person/) - }) -}) diff --git a/services/web/test/acceptance/src/CloseSiteTests.js b/services/web/test/acceptance/src/CloseSiteTests.js index 82748890ca..57ae3965e5 100644 --- a/services/web/test/acceptance/src/CloseSiteTests.js +++ b/services/web/test/acceptance/src/CloseSiteTests.js @@ -44,7 +44,7 @@ describe('siteIsOpen', function () { request.get('/some/route', { json: true }, (error, response, body) => { response.statusCode.should.equal(503) body.message.should.match(/maintenance/) - body.message.should.match(/status.overleaf.com/) + body.message.should.match(/status.example.com/) done() }) }) diff --git a/services/web/test/acceptance/src/DeletionTests.js b/services/web/test/acceptance/src/DeletionTests.js index eb2e533fd3..268cf0a368 100644 --- a/services/web/test/acceptance/src/DeletionTests.js +++ b/services/web/test/acceptance/src/DeletionTests.js @@ -4,8 +4,6 @@ const async = require('async') const { expect } = require('chai') const settings = require('@overleaf/settings') const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb') -const { Subscription } = require('../../../app/src/models/Subscription') -const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder') const MockDocstoreApiClass = require('./mocks/MockDocstoreApi') const MockFilestoreApiClass = require('./mocks/MockFilestoreApi') @@ -75,29 +73,6 @@ describe('Deleting a user', function () { }) }) - it('Should fail if the user has a subscription', function (done) { - Subscription.create( - { - admin_id: this.user._id, - manager_ids: [this.user._id], - planCode: 'collaborator', - }, - error => { - expect(error).not.to.exist - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - error => { - expect(error).not.to.exist - this.user.deleteUser(error => { - expect(error).to.exist - done() - }) - } - ) - } - ) - }) - it("Should delete the user's projects", function (done) { this.user.createProject('wombat', (error, projectId) => { expect(error).not.to.exist @@ -437,150 +412,4 @@ describe('Deleting a project', function () { }) }) }) - - describe('when the deleted project has deletedFiles', function () { - beforeEach('delete project', function (done) { - this.user.deleteProject(this.projectId, done) - }) - let fileId1, fileId2 - beforeEach('create files', function () { - // take a short cut and just allocate file ids - fileId1 = ObjectId() - fileId2 = ObjectId() - }) - const otherFileDetails = { - name: 'universe.jpg', - linkedFileData: null, - hash: 'ed19e7d6779b47d8c63f6fa5a21954dcfb6cac00', - deletedAt: new Date(), - } - beforeEach('insert deletedFiles', async function () { - const deletedFiles = [ - { _id: fileId1, ...otherFileDetails }, - { _id: fileId2, ...otherFileDetails }, - // duplicate entry - { _id: fileId1, ...otherFileDetails }, - ] - await db.deletedProjects.updateOne( - { 'deleterData.deletedProjectId': ObjectId(this.projectId) }, - { $set: { 'project.deletedFiles': deletedFiles } } - ) - }) - describe('when undelete the project', function () { - let admin - beforeEach('create admin', function (done) { - admin = new User() - async.series( - [ - cb => admin.ensureUserExists(cb), - cb => admin.ensureAdmin(cb), - cb => admin.login(cb), - ], - done - ) - }) - beforeEach('undelete project', function (done) { - admin.undeleteProject(this.projectId, done) - }) - - it('should not insert deletedFiles into the projects collection', function (done) { - this.user.getProject(this.projectId, (error, project) => { - if (error) return done(error) - expect(project.deletedFiles).to.deep.equal([]) - done() - }) - }) - - it('should insert unique entries into the deletedFiles collection', async function () { - const docs = await db.deletedFiles - .find({}, { sort: { _id: 1 } }) - .toArray() - expect(docs).to.deep.equal([ - { - _id: fileId1, - projectId: ObjectId(this.projectId), - ...otherFileDetails, - }, - { - _id: fileId2, - projectId: ObjectId(this.projectId), - ...otherFileDetails, - }, - ]) - }) - }) - }) - - describe('when the deleted project has deletedDocs', function () { - beforeEach('delete project', function (done) { - this.user.deleteProject(this.projectId, done) - }) - - let deletedDocs - beforeEach('set deletedDocs', function () { - deletedDocs = [ - { _id: ObjectId(), name: 'foo.tex', deletedAt: new Date() }, - { _id: ObjectId(), name: 'bar.tex', deletedAt: new Date() }, - ] - deletedDocs.forEach(doc => { - MockDocstoreApi.createLegacyDeletedDoc( - this.projectId, - doc._id.toString() - ) - }) - }) - - beforeEach('insert deletedDocs', async function () { - await db.deletedProjects.updateOne( - { 'deleterData.deletedProjectId': ObjectId(this.projectId) }, - { $set: { 'project.deletedDocs': deletedDocs } } - ) - }) - - it('should not see any doc names before', async function () { - const docs = MockDocstoreApi.getDeletedDocs(this.projectId) - expect(docs).to.deep.equal( - deletedDocs.map(doc => { - const { _id } = doc - return { _id: _id.toString(), name: undefined } - }) - ) - }) - - describe('when undeleting the project', function () { - let admin - beforeEach('create admin', function (done) { - admin = new User() - async.series( - [ - cb => admin.ensureUserExists(cb), - cb => admin.ensureAdmin(cb), - cb => admin.login(cb), - ], - done - ) - }) - beforeEach('undelete project', function (done) { - admin.undeleteProject(this.projectId, done) - }) - - it('should not insert deletedDocs into the projects collection', function (done) { - this.user.getProject(this.projectId, (error, project) => { - if (error) return done(error) - expect(project.deletedDocs).to.deep.equal([]) - done() - }) - }) - - it('should back fill deleted docs context', async function () { - const docs = MockDocstoreApi.getDeletedDocs(this.projectId) - expect(docs).to.deep.equal( - deletedDocs.map(doc => { - const { _id, name } = doc - return { _id: _id.toString(), name } - }) - ) - }) - }) - }) }) diff --git a/services/web/test/acceptance/src/ExportsTests.js b/services/web/test/acceptance/src/ExportsTests.js deleted file mode 100644 index 124dab8e88..0000000000 --- a/services/web/test/acceptance/src/ExportsTests.js +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint-disable - camelcase, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ - -const { expect } = require('chai') -const User = require('./helpers/User') - -const MockProjectHistoryApiClass = require('./mocks/MockProjectHistoryApi') -const MockV1ApiClass = require('./mocks/MockV1Api') - -let MockProjectHistoryApi, MockV1Api - -before(function () { - MockV1Api = MockV1ApiClass.instance() - MockProjectHistoryApi = MockProjectHistoryApiClass.instance() -}) - -describe('Exports', function () { - beforeEach(function (done) { - this.brand_variation_id = '18' - this.owner = new User() - return this.owner.login(error => { - if (error != null) { - throw error - } - return this.owner.createProject( - 'example-project', - { template: 'example' }, - (error, project_id) => { - this.project_id = project_id - if (error != null) { - throw error - } - return done() - } - ) - }) - }) - - describe('exporting a project', function () { - beforeEach(function (done) { - this.version = Math.floor(Math.random() * 10000) - MockProjectHistoryApi.setProjectVersion(this.project_id, this.version) - this.export_id = Math.floor(Math.random() * 10000) - MockV1Api.setExportId(this.export_id) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/export/${this.brand_variation_id}`, - json: true, - body: { - title: 'title', - description: 'description', - author: 'author', - license: 'other', - showSource: true, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - this.exportResponseBody = body - return done() - } - ) - }) - - it('should have sent correct data to v1', function (done) { - const { - project, - user, - destination, - options, - } = MockV1Api.getLastExportParams() - // project details should match - expect(project.id).to.equal(this.project_id) - expect(project.rootDocPath).to.equal('/main.tex') - // gallery details should match - expect(project.metadata.title).to.equal('title') - expect(project.metadata.description).to.equal('description') - expect(project.metadata.author).to.equal('author') - expect(project.metadata.license).to.equal('other') - expect(project.metadata.showSource).to.equal(true) - // version should match what was retrieved from project-history - expect(project.historyVersion).to.equal(this.version) - // user details should match - expect(user.id).to.equal(this.owner.id) - expect(user.email).to.equal(this.owner.email) - // brand-variation should match - expect(destination.brandVariationId).to.equal(this.brand_variation_id) - return done() - }) - - it('should have returned the export ID provided by v1', function (done) { - expect(this.exportResponseBody.export_v1_id).to.equal(this.export_id) - return done() - }) - }) -}) diff --git a/services/web/test/acceptance/src/FeatureUpdaterTests.js b/services/web/test/acceptance/src/FeatureUpdaterTests.js deleted file mode 100644 index 12d3cfef30..0000000000 --- a/services/web/test/acceptance/src/FeatureUpdaterTests.js +++ /dev/null @@ -1,395 +0,0 @@ -const { expect } = require('chai') -const UserHelper = require('./helpers/UserHelper') -const settings = require('@overleaf/settings') -const { ObjectId } = require('mongodb') -const { Subscription } = require('../../../app/src/models/Subscription') -const { User } = require('../../../app/src/models/User') -const FeaturesUpdater = require('../../../app/src/Features/Subscription/FeaturesUpdater') - -const MockV1ApiClass = require('./mocks/MockV1Api') -const logger = require('logger-sharelatex') -logger.logger.level('error') - -let MockV1Api - -before(function () { - MockV1Api = MockV1ApiClass.instance() -}) - -const syncUserAndGetFeatures = function (user, callback) { - FeaturesUpdater.refreshFeatures(user._id, 'test', error => { - if (error) { - return callback(error) - } - User.findById(user._id, (error, user) => { - if (error) { - return callback(error) - } - const { features } = user.toObject() - delete features.$init // mongoose internals - callback(null, features) - }) - }) -} - -describe('FeatureUpdater.refreshFeatures', function () { - let userHelper, user - beforeEach(async function () { - userHelper = await UserHelper.createUser() - user = userHelper.user - }) - - describe('when user has no subscriptions', function () { - it('should set their features to the basic set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - expect(features).to.deep.equal(settings.defaultFeatures) - done() - }) - }) - }) - - describe('when the user has an individual subscription', function () { - beforeEach(function () { - Subscription.create({ - admin_id: user._id, - manager_ids: [user._id], - planCode: 'collaborator', - customAccount: true, - }) - }) // returns a promise - - it('should set their features to the upgraded set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const plan = settings.plans.find( - plan => plan.planCode === 'collaborator' - ) - expect(features).to.deep.equal(plan.features) - done() - }) - }) - }) - - describe('when the user is in a group subscription', function () { - beforeEach(function () { - const groupAdminId = ObjectId() - Subscription.create({ - admin_id: groupAdminId, - manager_ids: [groupAdminId], - member_ids: [user._id], - groupAccount: true, - planCode: 'collaborator', - customAccount: true, - }) - }) // returns a promise - - it('should set their features to the upgraded set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const plan = settings.plans.find( - plan => plan.planCode === 'collaborator' - ) - expect(features).to.deep.equal(plan.features) - done() - }) - }) - }) - - describe('when the user has bonus features', function () { - beforeEach(function () { - return User.updateOne( - { - _id: user._id, - }, - { - refered_user_count: 10, - } - ) - }) // returns a promise - - it('should set their features to the bonus set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - expect(features).to.deep.equal( - Object.assign( - {}, - settings.defaultFeatures, - settings.bonus_features[9] - ) - ) - done() - }) - }) - }) - - describe('when the user has affiliations', function () { - let email2, institutionId, hostname - beforeEach(async function () { - institutionId = MockV1Api.createInstitution({ commonsAccount: true }) - hostname = 'institution.edu' - MockV1Api.addInstitutionDomain(institutionId, hostname, { - confirmed: true, - }) - email2 = `${user._id}@${hostname}` - userHelper = await UserHelper.loginUser( - userHelper.getDefaultEmailPassword() - ) - await userHelper.addEmail(email2) - this.institutionPlan = settings.plans.find( - plan => plan.planCode === settings.institutionPlanCode - ) - }) - - it('should not set their features if email is not confirmed', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - expect(features).to.deep.equal(settings.defaultFeatures) - done() - }) - }) - - describe('when email is confirmed', function () { - beforeEach(async function () { - await userHelper.confirmEmail(user._id, email2) - }) - - it('should set their features', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - expect(features).to.deep.equal(this.institutionPlan.features) - done() - }) - }) - - describe('when domain is not confirmed as part of institution', function () { - beforeEach(function () { - MockV1Api.updateInstitutionDomain(institutionId, hostname, { - confirmed: false, - }) - }) - it('should not set their features', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - expect(features).to.deep.equal(settings.defaultFeatures) - done() - }) - }) - }) - }) - }) - - describe('when the user is due bonus features and has extra features that no longer apply', function () { - beforeEach(function () { - return User.updateOne( - { - _id: user._id, - }, - { - refered_user_count: 10, - 'features.github': true, - } - ) - }) // returns a promise - - it('should set their features to the bonus set and downgrade the extras', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - expect(features).to.deep.equal( - Object.assign( - {}, - settings.defaultFeatures, - settings.bonus_features[9] - ) - ) - done() - }) - }) - }) - - describe('when the user has a v1 plan', function () { - beforeEach(function () { - MockV1Api.setUser(42, { plan_name: 'free' }) - return User.updateOne( - { - _id: user._id, - }, - { - overleaf: { - id: 42, - }, - } - ) - }) // returns a promise - - it('should set their features to the v1 plan', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const plan = settings.plans.find(plan => plan.planCode === 'v1_free') - expect(features).to.deep.equal(plan.features) - done() - }) - }) - }) - - describe('when the user has a v1 plan and bonus features', function () { - beforeEach(function () { - MockV1Api.setUser(42, { plan_name: 'free' }) - return User.updateOne( - { - _id: user._id, - }, - { - overleaf: { - id: 42, - }, - refered_user_count: 10, - } - ) - }) // returns a promise - - it('should set their features to the best of the v1 plan and bonus features', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const v1plan = settings.plans.find(plan => plan.planCode === 'v1_free') - const expectedFeatures = Object.assign( - {}, - v1plan.features, - settings.bonus_features[9] - ) - expect(features).to.deep.equal(expectedFeatures) - done() - }) - }) - }) - - describe('when the user has a group and personal subscription', function () { - beforeEach(function (done) { - const groupAdminId = ObjectId() - - Subscription.create( - { - admin_id: user._id, - manager_ids: [user._id], - planCode: 'professional', - customAccount: true, - }, - error => { - if (error) { - throw error - } - Subscription.create( - { - admin_id: groupAdminId, - manager_ids: [groupAdminId], - member_ids: [user._id], - groupAccount: true, - planCode: 'collaborator', - customAccount: true, - }, - done - ) - } - ) - }) - - it('should set their features to the best set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const plan = settings.plans.find( - plan => plan.planCode === 'professional' - ) - expect(features).to.deep.equal(plan.features) - done() - }) - }) - }) - - describe('when the notifyV1Flag is passed', function () { - beforeEach(function () { - User.updateOne( - { - _id: user._id, - }, - { - overleaf: { - id: 42, - }, - } - ) - }) // returns a promise - }) - - describe('when the user has features overrides', function () { - beforeEach(function () { - const futureDate = new Date() - futureDate.setDate(futureDate.getDate() + 1) - return User.updateOne( - { - _id: user._id, - }, - { - featuresOverrides: [ - { - features: { - github: true, - }, - }, - { - features: { - dropbox: true, - }, - expiresAt: new Date(1990, 12, 25), - }, - { - features: { - trackChanges: true, - }, - expiresAt: futureDate, - }, - ], - } - ) - }) // returns a promise - - it('should set their features to the overridden set', function (done) { - syncUserAndGetFeatures(user, (error, features) => { - if (error) { - throw error - } - const expectedFeatures = Object.assign(settings.defaultFeatures, { - github: true, - trackChanges: true, - }) - expect(features).to.deep.equal(expectedFeatures) - done() - }) - }) - }) - - it('should update featuresUpdatedAt', async function () { - user = (await UserHelper.getUser({ _id: user._id })).user - expect(user.featuresUpdatedAt).to.not.exist // no default set - await FeaturesUpdater.promises.refreshFeatures(user._id, 'test') - user = (await UserHelper.getUser({ _id: user._id })).user - const featuresUpdatedAt = user.featuresUpdatedAt - expect(featuresUpdatedAt).to.exist - // refresh again - await FeaturesUpdater.promises.refreshFeatures(user._id, 'test') - user = (await UserHelper.getUser({ _id: user._id })).user - expect(user.featuresUpdatedAt > featuresUpdatedAt).to.be.true - }) -}) diff --git a/services/web/test/acceptance/src/HistoryTests.js b/services/web/test/acceptance/src/HistoryTests.js deleted file mode 100644 index 19474df179..0000000000 --- a/services/web/test/acceptance/src/HistoryTests.js +++ /dev/null @@ -1,227 +0,0 @@ -/* eslint-disable - camelcase, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const { expect } = require('chai') - -const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb') -const User = require('./helpers/User') -const MockV1HistoryApiClass = require('./mocks/MockV1HistoryApi') - -let MockV1HistoryApi - -before(function () { - MockV1HistoryApi = MockV1HistoryApiClass.instance() -}) - -describe('History', function () { - beforeEach(function (done) { - this.owner = new User() - return this.owner.login(done) - }) - - describe('zip download of version', function () { - it('should stream the zip file of a version', function (done) { - return this.owner.createProject( - 'example-project', - (error, project_id) => { - this.project_id = project_id - if (error != null) { - return done(error) - } - this.v1_history_id = 42 - return db.projects.updateOne( - { - _id: ObjectId(this.project_id), - }, - { - $set: { - 'overleaf.history.id': this.v1_history_id, - }, - }, - error => { - if (error != null) { - return done(error) - } - return this.owner.request( - `/project/${this.project_id}/version/42/zip`, - (error, response, body) => { - if (error != null) { - return done(error) - } - expect(response.statusCode).to.equal(200) - expect(response.headers['content-type']).to.equal( - 'application/zip' - ) - expect(response.headers['content-disposition']).to.equal( - 'attachment; filename="example-project%20(Version%2042).zip"' - ) - expect(body).to.equal( - `Mock zip for ${this.v1_history_id} at version 42` - ) - return done() - } - ) - } - ) - } - ) - }) - - describe('request abort', function () { - // Optional manual verification: add unique logging statements into - // HistoryController._pipeHistoryZipToResponse - // in each of the `req.aborted` branches and confirm that each branch - // was covered. - beforeEach(function setupNewProject(done) { - this.owner.createProject('example-project', (error, project_id) => { - this.project_id = project_id - if (error) { - return done(error) - } - this.v1_history_id = 42 - db.projects.updateOne( - { _id: ObjectId(this.project_id) }, - { - $set: { - 'overleaf.history.id': this.v1_history_id, - }, - }, - done - ) - }) - }) - - it('should abort the upstream request', function (done) { - const request = this.owner.request( - `/project/${this.project_id}/version/100/zip` - ) - request.on('error', done) - request.on('response', response => { - expect(response.statusCode).to.equal(200) - let receivedChunks = 0 - response.on('data', () => { - receivedChunks++ - }) - response.resume() - - setTimeout(() => { - request.abort() - const receivedSoFar = receivedChunks - const sentSoFar = MockV1HistoryApi.sentChunks - // Ihe next assertions should verify that chunks are emitted - // and received -- the exact number is not important. - // In theory we are now emitting the 3rd chunk, - // so this should be exactly 3, to not make this - // test flaky, we allow +- 2 chunks. - expect(sentSoFar).to.be.within(1, 4) - expect(receivedSoFar).to.be.within(1, 4) - setTimeout(() => { - // The fake-s3 service should have stopped emitting chunks. - // Ff not, that would be +5 in an ideal world (1 every 100ms). - // On the happy-path (it stopped) it emitted +1 which was - // in-flight and another +1 before it received the abort. - expect(MockV1HistoryApi.sentChunks).to.be.below(sentSoFar + 5) - expect(MockV1HistoryApi.sentChunks).to.be.within( - sentSoFar, - sentSoFar + 2 - ) - done() - }, 500) - }, 200) - }) - }) - - it('should skip the v1-history request', function (done) { - const request = this.owner.request( - `/project/${this.project_id}/version/100/zip` - ) - setTimeout(() => { - // This is a race-condition to abort the request after the - // processing of all the the express middleware completed. - // In case we abort before they complete, we do not hit our - // abort logic, but express internal logic, which is OK. - request.abort() - }, 2) - request.on('error', done) - setTimeout(() => { - expect(MockV1HistoryApi.requestedZipPacks).to.equal(0) - done() - }, 500) - }) - - it('should skip the async-polling', function (done) { - const request = this.owner.request( - `/project/${this.project_id}/version/100/zip` - ) - MockV1HistoryApi.events.on('v1-history-pack-zip', () => { - request.abort() - }) - request.on('error', done) - setTimeout(() => { - expect(MockV1HistoryApi.fakeZipCall).to.equal(0) - done() - }, 3000) // initial polling delay is 2s - }) - - it('should skip the upstream request', function (done) { - const request = this.owner.request( - `/project/${this.project_id}/version/100/zip` - ) - MockV1HistoryApi.events.on('v1-history-pack-zip', () => { - setTimeout(() => { - request.abort() - }, 1000) - }) - request.on('error', done) - setTimeout(() => { - expect(MockV1HistoryApi.fakeZipCall).to.equal(0) - done() - }, 3000) // initial polling delay is 2s - }) - }) - - it('should return 402 for non-v2-history project', function (done) { - return this.owner.createProject('non-v2-project', (error, project_id) => { - this.project_id = project_id - if (error != null) { - return done(error) - } - return db.projects.updateOne( - { - _id: ObjectId(this.project_id), - }, - { - $unset: { - 'overleaf.history.id': true, - }, - }, - error => { - if (error != null) { - return done(error) - } - return this.owner.request( - `/project/${this.project_id}/version/42/zip`, - (error, response, body) => { - if (error != null) { - return done(error) - } - expect(response.statusCode).to.equal(402) - return done() - } - ) - } - ) - }) - }) - }) -}) diff --git a/services/web/test/acceptance/src/Init.js b/services/web/test/acceptance/src/Init.js index dad80ea8ed..33471df154 100644 --- a/services/web/test/acceptance/src/Init.js +++ b/services/web/test/acceptance/src/Init.js @@ -1,4 +1,5 @@ require('./helpers/InitApp') +const Features = require('../../../app/src/infrastructure/Features') const MockAnalyticsApi = require('./mocks/MockAnalyticsApi') const MockChatApi = require('./mocks/MockChatApi') @@ -8,7 +9,6 @@ const MockDocUpdaterApi = require('./mocks/MockDocUpdaterApi') const MockFilestoreApi = require('./mocks/MockFilestoreApi') const MockNotificationsApi = require('./mocks/MockNotificationsApi') const MockProjectHistoryApi = require('./mocks/MockProjectHistoryApi') -const MockRecurlyApi = require('./mocks/MockRecurlyApi') const MockSpellingApi = require('./mocks/MockSpellingApi') const MockV1Api = require('./mocks/MockV1Api') const MockV1HistoryApi = require('./mocks/MockV1HistoryApi') @@ -17,15 +17,17 @@ const mockOpts = { debug: ['1', 'true', 'TRUE'].includes(process.env.DEBUG_MOCKS), } -MockAnalyticsApi.initialize(3050, mockOpts) MockChatApi.initialize(3010, mockOpts) MockClsiApi.initialize(3013, mockOpts) MockDocstoreApi.initialize(3016, mockOpts) MockDocUpdaterApi.initialize(3003, mockOpts) MockFilestoreApi.initialize(3009, mockOpts) MockNotificationsApi.initialize(3042, mockOpts) -MockProjectHistoryApi.initialize(3054, mockOpts) -MockRecurlyApi.initialize(6034, mockOpts) MockSpellingApi.initialize(3005, mockOpts) -MockV1Api.initialize(5000, mockOpts) -MockV1HistoryApi.initialize(3100, mockOpts) + +if (Features.hasFeature('saas')) { + MockAnalyticsApi.initialize(3050, mockOpts) + MockProjectHistoryApi.initialize(3054, mockOpts) + MockV1Api.initialize(5000, mockOpts) + MockV1HistoryApi.initialize(3100, mockOpts) +} diff --git a/services/web/test/acceptance/src/LabelsTests.js b/services/web/test/acceptance/src/LabelsTests.js deleted file mode 100644 index 1a0183f7ca..0000000000 --- a/services/web/test/acceptance/src/LabelsTests.js +++ /dev/null @@ -1,128 +0,0 @@ -/* eslint-disable - camelcase, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const _ = require('underscore') -const { expect } = require('chai') -const { ObjectId } = require('mongodb') -const request = require('./helpers/request') - -const User = require('./helpers/User') -const MockProjectHistoryApiClass = require('./mocks/MockProjectHistoryApi') - -let MockProjectHistoryApi - -before(function () { - MockProjectHistoryApi = MockProjectHistoryApiClass.instance() -}) - -describe('Labels', function () { - beforeEach(function (done) { - this.owner = new User() - return this.owner.login(error => { - if (error != null) { - throw error - } - return this.owner.createProject( - 'example-project', - { template: 'example' }, - (error, project_id) => { - this.project_id = project_id - if (error != null) { - throw error - } - return done() - } - ) - }) - }) - - it('getting labels', function (done) { - const label_id = new ObjectId().toString() - const comment = 'a label comment' - const version = 3 - MockProjectHistoryApi.addLabel(this.project_id, { - id: label_id, - comment, - version, - }) - - return this.owner.request( - { - method: 'GET', - url: `/project/${this.project_id}/labels`, - json: true, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - expect(body).to.deep.equal([{ id: label_id, comment, version }]) - return done() - } - ) - }) - - it('creating a label', function (done) { - const comment = 'a label comment' - const version = 3 - - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/labels`, - json: { comment, version }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - const { label_id } = body - expect(MockProjectHistoryApi.getLabels(this.project_id)).to.deep.equal([ - { id: label_id, comment, version }, - ]) - return done() - } - ) - }) - - it('deleting a label', function (done) { - const label_id = new ObjectId().toString() - const comment = 'a label comment' - const version = 3 - MockProjectHistoryApi.addLabel(this.project_id, { - id: label_id, - comment, - version, - }) - - return this.owner.request( - { - method: 'DELETE', - url: `/project/${this.project_id}/labels/${label_id}`, - json: true, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(204) - expect(MockProjectHistoryApi.getLabels(this.project_id)).to.deep.equal( - [] - ) - return done() - } - ) - }) -}) diff --git a/services/web/test/acceptance/src/ProjectInviteTests.js b/services/web/test/acceptance/src/ProjectInviteTests.js index b6124b8e86..2769accef2 100644 --- a/services/web/test/acceptance/src/ProjectInviteTests.js +++ b/services/web/test/acceptance/src/ProjectInviteTests.js @@ -631,7 +631,7 @@ describe('ProjectInviteTests', function () { describe('user is not logged in initially', function () { describe('registration prompt workflow with valid token', function () { before(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) @@ -662,7 +662,7 @@ describe('ProjectInviteTests', function () { describe('registration prompt workflow with non-valid token', function () { before(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) diff --git a/services/web/test/acceptance/src/ProjectStructureTests.js b/services/web/test/acceptance/src/ProjectStructureTests.js index 20867c603f..9222d251fa 100644 --- a/services/web/test/acceptance/src/ProjectStructureTests.js +++ b/services/web/test/acceptance/src/ProjectStructureTests.js @@ -1,10 +1,7 @@ const { expect } = require('chai') -const mkdirp = require('mkdirp') const { ObjectId } = require('mongodb') const Path = require('path') const fs = require('fs') -const Settings = require('@overleaf/settings') -const _ = require('underscore') const { Project } = require('../../../app/src/models/Project') const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter.js') @@ -94,34 +91,6 @@ describe('ProjectStructureChanges', function () { ) } - function uploadFile( - owner, - projectId, - folderId, - file, - name, - contentType, - callback - ) { - owner.uploadFileInProject( - projectId, - folderId, - file, - name, - contentType, - callback - ) - } - - function uploadExampleFile(owner, projectId, folderId, callback) { - owner.uploadExampleFileInProject( - projectId, - folderId, - '1pixel.png', - callback - ) - } - function uploadExampleProject(owner, zipFilename, options, callback) { if (typeof options === 'function') { callback = options @@ -154,193 +123,10 @@ describe('ProjectStructureChanges', function () { ) } - function moveItem(owner, projectId, type, itemId, folderId, callback) { - owner.moveItemInProject(projectId, type, itemId, folderId, callback) - } - - function renameItem(owner, projectId, type, itemId, name, callback) { - owner.renameItemInProject(projectId, type, itemId, name, callback) - } - function deleteItem(owner, projectId, type, itemId, callback) { owner.deleteItemInProject(projectId, type, itemId, callback) } - function verifyVersionIncremented( - projectId, - oldVersion, - updateVersion, - increment, - callback - ) { - expect(updateVersion).to.equal(oldVersion + increment) - - ProjectGetter.getProject(projectId, (error, newProject) => { - if (error) { - return callback(error) - } - - expect(newProject.version).to.equal(updateVersion) - callback() - }) - } - - describe('creating a project from the example template', function () { - let exampleProjectId - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId) => { - exampleProjectId = projectId - done(err) - }) - }) - - it('should version creating a doc and a file', function () { - const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(3) - for (const update of updates.slice(0, 2)) { - expect(update.type).to.equal('add-doc') - expect(update.userId).to.equal(owner._id) - expect(update.docLines).to.be.a('string') - } - expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1) - expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal( - 1 - ) - expect(updates[2].type).to.equal('add-file') - expect(updates[2].userId).to.equal(owner._id) - expect(updates[2].pathname).to.equal('/universe.jpg') - expect(updates[2].url).to.be.a('string') - expect(version).to.equal(3) - }) - }) - - describe('duplicating a project', function () { - let dupProjectId - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId) => { - if (err) { - return done(err) - } - owner.request.post( - { - uri: `/Project/${projectId}/clone`, - json: { - projectName: 'new.tex', - }, - }, - (error, res, body) => { - if (error) { - throw error - } - if (res.statusCode < 200 || res.statusCode >= 300) { - throw new Error(`failed to clone project ${res.statusCode}`) - } - dupProjectId = body.project_id - done() - } - ) - }) - }) - - it('should version the docs and files created', function () { - const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates( - dupProjectId - ) - expect(updates.length).to.equal(3) - for (const update of updates.slice(0, 2)) { - expect(update.type).to.equal('add-doc') - expect(update.userId).to.equal(owner._id) - expect(update.docLines).to.be.a('string') - } - expect(_.where(updates, { pathname: '/main.tex' }).length).to.equal(1) - expect(_.where(updates, { pathname: '/references.bib' }).length).to.equal( - 1 - ) - expect(updates[2].type).to.equal('add-file') - expect(updates[2].userId).to.equal(owner._id) - expect(updates[2].pathname).to.equal('/universe.jpg') - expect(updates[2].url).to.be.a('string') - expect(version).to.equal(1) - }) - }) - - describe('adding a doc', function () { - let exampleProjectId, oldVersion - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - MockDocUpdaterApi.reset() - - ProjectGetter.getProject(projectId, (error, project) => { - if (error) { - return done(error) - } - oldVersion = project.version - createExampleDoc(owner, projectId, done) - }) - }) - }) - - it('should version the doc added', function (done) { - const { - updates, - version: newVersion, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/new.tex') - expect(update.docLines).to.be.a('string') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - newVersion, - 1, - done - ) - }) - }) - - describe('uploading a project', function () { - let exampleProjectId - - beforeEach(function (done) { - uploadExampleProject(owner, 'test_project.zip', (err, projectId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - done() - }) - }) - - it('should version the docs and files created', function () { - const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(2) - expect(updates[0].type).to.equal('add-doc') - expect(updates[0].userId).to.equal(owner._id) - expect(updates[0].pathname).to.equal('/main.tex') - expect(updates[0].docLines).to.equal('Test') - expect(updates[1].type).to.equal('add-file') - expect(updates[1].userId).to.equal(owner._id) - expect(updates[1].pathname).to.equal('/1pixel.png') - expect(updates[1].url).to.be.a('string') - expect(version).to.equal(1) - }) - }) - describe('uploading a project with a name', function () { let exampleProjectId const testProjectName = 'wombat' @@ -496,509 +282,6 @@ describe('ProjectStructureChanges', function () { }) }) - describe('uploading a project with files in different encodings', function () { - let updates - beforeEach(function (done) { - uploadExampleProject(owner, 'charsets/charsets.zip', (err, projectId) => { - if (err) { - return done(err) - } - - updates = MockDocUpdaterApi.getProjectStructureUpdates(projectId) - .updates - done() - }) - }) - - it('should correctly parse windows-1252', function () { - const update = _.find( - updates, - update => update.pathname === '/test-german-windows-1252.tex' - ) - expect(update.docLines).to.contain( - 'Der schnelle braune Fuchs sprang träge über den Hund.' - ) - }) - - it('should correctly parse German utf8', function () { - const update = _.find( - updates, - update => update.pathname === '/test-german-utf8x.tex' - ) - expect(update.docLines).to.contain( - 'Der schnelle braune Fuchs sprang träge über den Hund.' - ) - }) - - it('should correctly parse little-endian utf16', function () { - const update = _.find( - updates, - update => update.pathname === '/test-greek-utf16-le-bom.tex' - ) - expect(update.docLines).to.contain( - 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.' - ) - }) - - it('should correctly parse Greek utf8', function () { - const update = _.find( - updates, - update => update.pathname === '/test-greek-utf8x.tex' - ) - expect(update.docLines).to.contain( - 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.' - ) - }) - }) - - describe('uploading a file', function () { - let exampleProjectId, oldVersion, rootFolderId - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId, folderId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - rootFolderId = folderId - MockDocUpdaterApi.reset() - ProjectGetter.getProject(projectId, (error, project) => { - if (error) { - throw error - } - - oldVersion = project.version - - uploadExampleFile(owner, projectId, rootFolderId, done) - }) - }) - }) - - it('should version a newly uploaded file', function (done) { - const { updates, version } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-file') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/1pixel.png') - expect(update.url).to.be.a('string') - - // one file upload - verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done) - }) - - it('should version a replacement file', function (done) { - MockDocUpdaterApi.reset() - - uploadFile( - owner, - exampleProjectId, - rootFolderId, - '2pixel.png', - '1pixel.png', - 'image/png', - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(2) - expect(updates[0].type).to.equal('rename-file') - expect(updates[0].userId).to.equal(owner._id) - expect(updates[0].pathname).to.equal('/1pixel.png') - expect(updates[0].newPathname).to.equal('') - expect(updates[1].type).to.equal('add-file') - expect(updates[1].userId).to.equal(owner._id) - expect(updates[1].pathname).to.equal('/1pixel.png') - expect(updates[1].url).to.be.a('string') - - // two file uploads - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 2, - done - ) - } - ) - }) - }) - - describe('moving entities', function () { - let exampleProjectId, - oldVersion, - exampleDocId, - exampleFileId, - exampleFolderId - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId, rootFolderId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - createExampleDoc(owner, projectId, (err, docId) => { - if (err) { - return done(err) - } - exampleDocId = docId - uploadExampleFile(owner, projectId, rootFolderId, (err, fileId) => { - if (err) { - return done(err) - } - exampleFileId = fileId - createExampleFolder(owner, projectId, (err, folderId) => { - if (err) { - return done(err) - } - exampleFolderId = folderId - - ProjectGetter.getProject(projectId, (error, project) => { - if (error) { - throw error - } - oldVersion = project.version - MockDocUpdaterApi.reset() - done() - }) - }) - }) - }) - }) - }) - - it('should version moving a doc', function (done) { - moveItem( - owner, - exampleProjectId, - 'doc', - exampleDocId, - exampleFolderId, - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/new.tex') - expect(update.newPathname).to.equal('/foo/new.tex') - - // 2, because it's a delete and then add - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 2, - done - ) - } - ) - }) - - it('should version moving a file', function (done) { - moveItem( - owner, - exampleProjectId, - 'file', - exampleFileId, - exampleFolderId, - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-file') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/1pixel.png') - expect(update.newPathname).to.equal('/foo/1pixel.png') - - // 2, because it's a delete and then add - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 2, - done - ) - } - ) - }) - - it('should version moving a folder', function (done) { - moveItem( - owner, - exampleProjectId, - 'doc', - exampleDocId, - exampleFolderId, - () => { - MockDocUpdaterApi.reset() - - owner.request.post( - { - uri: `project/${exampleProjectId}/folder`, - json: { - name: 'bar', - }, - }, - (error, res, body) => { - if (error) { - throw error - } - const newFolderId = body._id - - moveItem( - owner, - exampleProjectId, - 'folder', - exampleFolderId, - newFolderId, - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/foo/new.tex') - expect(update.newPathname).to.equal('/bar/foo/new.tex') - - // 5, because it's two file moves plus a folder - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 5, - done - ) - } - ) - } - ) - } - ) - }) - }) - - describe('renaming entities', function () { - let exampleProjectId, - exampleDocId, - exampleFileId, - exampleFolderId, - oldVersion - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId, rootFolderId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - createExampleDoc(owner, projectId, (err, docId) => { - if (err) { - return done(err) - } - exampleDocId = docId - uploadExampleFile(owner, projectId, rootFolderId, (err, fileId) => { - if (err) { - return done(err) - } - exampleFileId = fileId - createExampleFolder(owner, projectId, (err, folderId) => { - if (err) { - return done(err) - } - exampleFolderId = folderId - moveItem(owner, projectId, 'doc', docId, folderId, () => { - moveItem(owner, projectId, 'file', fileId, folderId, () => { - MockDocUpdaterApi.reset() - ProjectGetter.getProject( - exampleProjectId, - (error, project) => { - if (error) { - throw error - } - oldVersion = project.version - done() - } - ) - }) - }) - }) - }) - }) - }) - }) - - it('should version renaming a doc', function (done) { - renameItem( - owner, - exampleProjectId, - 'Doc', - exampleDocId, - 'wombat.tex', - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/foo/new.tex') - expect(update.newPathname).to.equal('/foo/wombat.tex') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 1, - done - ) - } - ) - }) - - it('should version renaming a file', function (done) { - renameItem( - owner, - exampleProjectId, - 'file', - exampleFileId, - 'potato.png', - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-file') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/foo/1pixel.png') - expect(update.newPathname).to.equal('/foo/potato.png') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 1, - done - ) - } - ) - }) - - it('should version renaming a folder', function (done) { - renameItem( - owner, - exampleProjectId, - 'folder', - exampleFolderId, - 'giraffe', - () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(2) - expect(updates[0].type).to.equal('rename-doc') - expect(updates[0].userId).to.equal(owner._id) - expect(updates[0].pathname).to.equal('/foo/new.tex') - expect(updates[0].newPathname).to.equal('/giraffe/new.tex') - expect(updates[1].type).to.equal('rename-file') - expect(updates[1].userId).to.equal(owner._id) - expect(updates[1].pathname).to.equal('/foo/1pixel.png') - expect(updates[1].newPathname).to.equal('/giraffe/1pixel.png') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 1, - done - ) - } - ) - }) - }) - - describe('deleting entities', function () { - let exampleProjectId, oldVersion, exampleFolderId - - beforeEach(function (done) { - createExampleProject(owner, (err, projectId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - createExampleFolder(owner, exampleProjectId, (err, folderId) => { - if (err) { - return done(err) - } - exampleFolderId = folderId - createExampleDoc(owner, projectId, (err, docId) => { - if (err) { - return done(err) - } - uploadExampleFile(owner, projectId, folderId, (err, fileId) => { - if (err) { - return done(err) - } - moveItem(owner, projectId, 'doc', docId, folderId, () => { - moveItem(owner, projectId, 'file', fileId, folderId, () => { - MockDocUpdaterApi.reset() - ProjectGetter.getProject( - exampleProjectId, - (error, project) => { - if (error) { - throw error - } - oldVersion = project.version - done() - } - ) - }) - }) - }) - }) - }) - }) - }) - - it('should version deleting a folder', function (done) { - deleteItem(owner, exampleProjectId, 'folder', exampleFolderId, () => { - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(2) - expect(updates[0].type).to.equal('rename-doc') - expect(updates[0].userId).to.equal(owner._id) - expect(updates[0].pathname).to.equal('/foo/new.tex') - expect(updates[0].newPathname).to.equal('') - expect(updates[1].type).to.equal('rename-file') - expect(updates[1].userId).to.equal(owner._id) - expect(updates[1].pathname).to.equal('/foo/1pixel.png') - expect(updates[1].newPathname).to.equal('') - - verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done) - }) - }) - }) - describe('deleting docs', function () { beforeEach(function (done) { createExampleProject(owner, (err, projectId) => { @@ -1113,292 +396,4 @@ describe('ProjectStructureChanges', function () { }) }) }) - - describe('tpds', function () { - let projectName, exampleProjectId, oldVersion, rootFolderId - - beforeEach(function (done) { - projectName = `tpds-project-${new ObjectId().toString()}` - owner.createProject(projectName, (error, projectId) => { - if (error) { - throw error - } - exampleProjectId = projectId - mkdirp(Settings.path.dumpFolder, () => { - ProjectGetter.getProject(exampleProjectId, (error, project) => { - if (error) { - throw error - } - MockDocUpdaterApi.reset() - rootFolderId = project.rootFolder[0]._id.toString() - oldVersion = project.version - done() - }) - }) - }) - }) - - it('should version adding a doc', function (done) { - const texFile = fs.createReadStream( - Path.resolve(Path.join(__dirname, '..', 'files', 'test.tex')) - ) - - const req = owner.request.post({ - uri: `/user/${owner._id}/update/${projectName}/test.tex`, - auth: { - user: _.keys(Settings.httpAuthUsers)[0], - pass: _.values(Settings.httpAuthUsers)[0], - sendImmediately: true, - }, - }) - - texFile.on('error', err => { - throw err - }) - - req.on('error', err => { - throw err - }) - - req.on('response', res => { - if (res.statusCode < 200 || res.statusCode >= 300) { - throw new Error(`failed to upload file ${res.statusCode}`) - } - - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/test.tex') - expect(update.docLines).to.equal('Test') - - verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done) - }) - - texFile.pipe(req) - }) - - it('should version adding a new file', function (done) { - const imageFile = fs.createReadStream( - Path.resolve(Path.join(__dirname, '..', 'files', '1pixel.png')) - ) - - const req = owner.request.post({ - uri: `/user/${owner._id}/update/${projectName}/1pixel.png`, - auth: { - user: _.keys(Settings.httpAuthUsers)[0], - pass: _.values(Settings.httpAuthUsers)[0], - sendImmediately: true, - }, - }) - - imageFile.on('error', err => { - throw err - }) - - req.on('error', err => { - throw err - }) - - req.on('response', res => { - if (res.statusCode < 200 || res.statusCode >= 300) { - throw new Error(`failed to upload file ${res.statusCode}`) - } - - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-file') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/1pixel.png') - expect(update.url).to.be.a('string') - - verifyVersionIncremented(exampleProjectId, oldVersion, version, 1, done) - }) - - imageFile.pipe(req) - }) - - describe('when there are files in the project', function () { - beforeEach(function (done) { - uploadExampleFile(owner, exampleProjectId, rootFolderId, () => { - createExampleDoc(owner, exampleProjectId, () => { - ProjectGetter.getProject(exampleProjectId, (error, project) => { - if (error) { - throw error - } - MockDocUpdaterApi.reset() - oldVersion = project.version - done() - }) - }) - }) - }) - - it('should version replacing a file', function (done) { - const imageFile = fs.createReadStream( - Path.resolve(Path.join(__dirname, '..', 'files', '2pixel.png')) - ) - - const req = owner.request.post({ - uri: `/user/${owner._id}/update/${projectName}/1pixel.png`, - auth: { - user: _.keys(Settings.httpAuthUsers)[0], - pass: _.values(Settings.httpAuthUsers)[0], - sendImmediately: true, - }, - }) - - imageFile.on('error', err => { - throw err - }) - - req.on('error', err => { - throw err - }) - - req.on('response', res => { - if (res.statusCode < 200 || res.statusCode >= 300) { - throw new Error(`failed to upload file ${res.statusCode}`) - } - - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(2) - expect(updates[0].type).to.equal('rename-file') - expect(updates[0].userId).to.equal(owner._id) - expect(updates[0].pathname).to.equal('/1pixel.png') - expect(updates[0].newPathname).to.equal('') - expect(updates[1].type).to.equal('add-file') - expect(updates[1].userId).to.equal(owner._id) - expect(updates[1].pathname).to.equal('/1pixel.png') - expect(updates[1].url).to.be.a('string') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 1, - done - ) - }) - - imageFile.pipe(req) - }) - - it('should version deleting a doc', function (done) { - owner.request.delete( - { - uri: `/user/${owner._id}/update/${projectName}/new.tex`, - auth: { - user: _.keys(Settings.httpAuthUsers)[0], - pass: _.values(Settings.httpAuthUsers)[0], - sendImmediately: true, - }, - }, - (error, res) => { - if (error) { - throw error - } - if (res.statusCode < 200 || res.statusCode >= 300) { - throw new Error(`failed to delete doc ${res.statusCode}`) - } - - const { - updates, - version, - } = MockDocUpdaterApi.getProjectStructureUpdates(exampleProjectId) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('rename-doc') - expect(update.userId).to.equal(owner._id) - expect(update.pathname).to.equal('/new.tex') - expect(update.newPathname).to.equal('') - - verifyVersionIncremented( - exampleProjectId, - oldVersion, - version, - 1, - done - ) - } - ) - }) - }) - }) - - describe('uploading a document', function () { - let exampleProjectId, rootFolderId - beforeEach(function (done) { - createExampleProject(owner, (err, projectId, folderId) => { - if (err) { - return done(err) - } - exampleProjectId = projectId - rootFolderId = folderId - MockDocUpdaterApi.reset() - done() - }) - }) - - describe('with an unusual character set', function () { - it('should correctly handle utf16-le data', function (done) { - uploadFile( - owner, - exampleProjectId, - rootFolderId, - 'charsets/test-greek-utf16-le-bom.tex', - 'test-greek-utf16-le-bom.tex', - 'text/x-tex', - () => { - const { updates } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-doc') - expect(update.pathname).to.equal('/test-greek-utf16-le-bom.tex') - expect(update.docLines).to.contain( - 'Η γρήγορη καστανή αλεπού πήδηξε χαλαρά πάνω από το σκυλί.' - ) - done() - } - ) - }) - - it('should correctly handle windows1252/iso-8859-1/latin1 data', function (done) { - uploadFile( - owner, - exampleProjectId, - rootFolderId, - 'charsets/test-german-windows-1252.tex', - 'test-german-windows-1252.tex', - 'text/x-tex', - () => { - const { updates } = MockDocUpdaterApi.getProjectStructureUpdates( - exampleProjectId - ) - expect(updates.length).to.equal(1) - const update = updates[0] - expect(update.type).to.equal('add-doc') - expect(update.pathname).to.equal('/test-german-windows-1252.tex') - expect(update.docLines).to.contain( - 'Der schnelle braune Fuchs sprang träge über den Hund.' - ) - done() - } - ) - }) - }) - }) }) diff --git a/services/web/test/acceptance/src/ProxyUrls.js b/services/web/test/acceptance/src/ProxyUrls.js deleted file mode 100644 index f51cc7bba0..0000000000 --- a/services/web/test/acceptance/src/ProxyUrls.js +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const { assert, expect } = require('chai') -const async = require('async') -const request = require('./helpers/request') - -const assertResponse = (path, expectedStatusCode, expectedBody, cb) => - request.get(path, (error, response) => { - expect(error).not.to.exist - response.statusCode.should.equal(expectedStatusCode) - if (expectedBody) { - assert.deepEqual(JSON.parse(response.body), expectedBody) - } - return cb() - }) - -describe('ProxyUrls', function () { - beforeEach(function () { - return this.timeout(1000) - }) - - it('proxy static URLs', function (done) { - return async.series( - [ - cb => assertResponse('/institutions/list', 200, [], cb), - cb => assertResponse('/institutions/domains', 200, [], cb), - ], - done - ) - }) - - it('proxy dynamic URLs', function (done) { - return async.series( - [ - cb => - assertResponse( - '/institutions/list/123', - 200, - { id: 123, name: 'Institution 123' }, - cb - ), - cb => - assertResponse( - '/institutions/list/456', - 200, - { id: 456, name: 'Institution 456' }, - cb - ), - ], - done - ) - }) - - it('return 404 if proxy is not set', function (done) { - return async.series( - [cb => assertResponse('/institutions/foobar', 404, null, cb)], - done - ) - }) - - it('handle missing baseUrl', function (done) { - return async.series( - [cb => assertResponse('/proxy/missing/baseUrl', 500, null, cb)], - done - ) - }) -}) diff --git a/services/web/test/acceptance/src/RecurlySubscriptionUpdateTests.js b/services/web/test/acceptance/src/RecurlySubscriptionUpdateTests.js deleted file mode 100644 index 01206aa035..0000000000 --- a/services/web/test/acceptance/src/RecurlySubscriptionUpdateTests.js +++ /dev/null @@ -1,43 +0,0 @@ -const { expect } = require('chai') -const async = require('async') -const User = require('./helpers/User') -const RecurlySubscription = require('./helpers/RecurlySubscription') - -describe('Subscriptions', function () { - describe('update', function () { - beforeEach(function (done) { - this.recurlyUser = new User() - async.series( - [ - cb => this.recurlyUser.ensureUserExists(cb), - cb => { - this.recurlySubscription = new RecurlySubscription({ - adminId: this.recurlyUser._id, - account: { - email: 'stale-recurly@email.com', - }, - }) - this.recurlySubscription.ensureExists(cb) - }, - cb => this.recurlyUser.login(cb), - ], - done - ) - }) - - it('updates the email address for the account', function (done) { - const url = '/user/subscription/account/email' - - this.recurlyUser.request.post({ url }, (error, { statusCode }) => { - if (error) { - return done(error) - } - expect(statusCode).to.equal(200) - expect(this.recurlyUser.email).to.equal( - this.recurlySubscription.account.email - ) - done() - }) - }) - }) -}) diff --git a/services/web/test/acceptance/src/RegistrationTests.js b/services/web/test/acceptance/src/RegistrationTests.js index 7725db4d63..34e97c2108 100644 --- a/services/web/test/acceptance/src/RegistrationTests.js +++ b/services/web/test/acceptance/src/RegistrationTests.js @@ -10,9 +10,6 @@ const UserPromises = require('./helpers/User').promises const redis = require('./helpers/redis') const Features = require('../../../app/src/infrastructure/Features') -// Currently this is testing registration via the 'public-registration' module, -// whereas in production we're using the 'overleaf-integration' module. - // Expectations const expectProjectAccess = function (user, projectId, callback) { // should have access to project @@ -220,7 +217,7 @@ describe('Registration', function () { describe('CSRF protection', function () { before(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) @@ -311,7 +308,7 @@ describe('Registration', function () { describe('Register', function () { before(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) @@ -333,39 +330,6 @@ describe('Registration', function () { }) }) - describe('Register with bonus referal id', function () { - before(function () { - if (!Features.hasFeature('public-registration')) { - this.skip() - } - }) - - beforeEach(function (done) { - this.user1 = new User() - this.user2 = new User() - async.series( - [ - cb => this.user1.register(cb), - cb => - this.user2.registerWithQuery( - `?r=${this.user1.referal_id}&rm=d&rs=b`, - cb - ), - ], - done - ) - }) - - it('Adds a referal when an id is supplied and the referal source is "bonus"', function (done) { - this.user1.get((error, user) => { - expect(error).to.not.exist - user.refered_user_count.should.eql(1) - - return done() - }) - }) - }) - describe('LoginViaRegistration', function () { beforeEach(function (done) { this.timeout(60000) @@ -387,7 +351,7 @@ describe('Registration', function () { describe('[Security] Trying to register/login as another user', function () { before(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) diff --git a/services/web/test/acceptance/src/RestoringFilesTest.js b/services/web/test/acceptance/src/RestoringFilesTest.js index 560f89ece1..5a0abeba69 100644 --- a/services/web/test/acceptance/src/RestoringFilesTest.js +++ b/services/web/test/acceptance/src/RestoringFilesTest.js @@ -11,23 +11,16 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require('async') const { expect } = require('chai') const _ = require('underscore') -const fs = require('fs') -const Path = require('path') - -const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter.js') const User = require('./helpers/User') -const MockProjectHistoryApiClass = require('./mocks/MockProjectHistoryApi') const MockDocstoreApiClass = require('./mocks/MockDocstoreApi') const MockFilestoreApiClass = require('./mocks/MockFilestoreApi') -let MockProjectHistoryApi, MockDocstoreApi, MockFilestoreApi +let MockDocstoreApi, MockFilestoreApi before(function () { - MockProjectHistoryApi = MockProjectHistoryApiClass.instance() MockDocstoreApi = MockDocstoreApiClass.instance() MockFilestoreApi = MockFilestoreApiClass.instance() }) @@ -116,242 +109,4 @@ describe('RestoringFiles', function () { }) }) }) - - describe('restoring from v2 history', function () { - describe('restoring a text file', function () { - beforeEach(function (done) { - MockProjectHistoryApi.addOldFile( - this.project_id, - 42, - 'foo.tex', - 'hello world, this is foo.tex!' - ) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/restore_file`, - json: { - pathname: 'foo.tex', - version: 42, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return done() - } - ) - }) - - it('should have created a doc', function (done) { - return this.owner.getProject(this.project_id, (error, project) => { - if (error != null) { - throw error - } - let doc = _.find( - project.rootFolder[0].docs, - doc => doc.name === 'foo.tex' - ) - doc = MockDocstoreApi.docs[this.project_id][doc._id] - expect(doc.lines).to.deep.equal(['hello world, this is foo.tex!']) - return done() - }) - }) - }) - - describe('restoring a binary file', function () { - beforeEach(function (done) { - this.pngData = fs.readFileSync( - Path.resolve(__dirname, '../files/1pixel.png'), - 'binary' - ) - MockProjectHistoryApi.addOldFile( - this.project_id, - 42, - 'image.png', - this.pngData - ) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/restore_file`, - json: { - pathname: 'image.png', - version: 42, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return done() - } - ) - }) - - it('should have created a file', function (done) { - return this.owner.getProject(this.project_id, (error, project) => { - if (error != null) { - throw error - } - let file = _.find( - project.rootFolder[0].fileRefs, - file => file.name === 'image.png' - ) - file = MockFilestoreApi.files[this.project_id][file._id] - expect(file.content).to.equal(this.pngData) - return done() - }) - }) - }) - - describe('restoring to a directory that exists', function () { - beforeEach(function (done) { - MockProjectHistoryApi.addOldFile( - this.project_id, - 42, - 'foldername/foo2.tex', - 'hello world, this is foo-2.tex!' - ) - return this.owner.request.post( - { - uri: `project/${this.project_id}/folder`, - json: { - name: 'foldername', - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/restore_file`, - json: { - pathname: 'foldername/foo2.tex', - version: 42, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return done() - } - ) - } - ) - }) - - it('should have created the doc in the named folder', function (done) { - return this.owner.getProject(this.project_id, (error, project) => { - if (error != null) { - throw error - } - const folder = _.find( - project.rootFolder[0].folders, - folder => folder.name === 'foldername' - ) - let doc = _.find(folder.docs, doc => doc.name === 'foo2.tex') - doc = MockDocstoreApi.docs[this.project_id][doc._id] - expect(doc.lines).to.deep.equal(['hello world, this is foo-2.tex!']) - return done() - }) - }) - }) - - describe('restoring to a directory that no longer exists', function () { - beforeEach(function (done) { - MockProjectHistoryApi.addOldFile( - this.project_id, - 42, - 'nothere/foo3.tex', - 'hello world, this is foo-3.tex!' - ) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/restore_file`, - json: { - pathname: 'nothere/foo3.tex', - version: 42, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return done() - } - ) - }) - - it('should have created the folder and restored the doc to it', function (done) { - return this.owner.getProject(this.project_id, (error, project) => { - if (error != null) { - throw error - } - const folder = _.find( - project.rootFolder[0].folders, - folder => folder.name === 'nothere' - ) - expect(folder).to.exist - let doc = _.find(folder.docs, doc => doc.name === 'foo3.tex') - doc = MockDocstoreApi.docs[this.project_id][doc._id] - expect(doc.lines).to.deep.equal(['hello world, this is foo-3.tex!']) - return done() - }) - }) - }) - - describe('restoring to a filename that already exists', function () { - beforeEach(function (done) { - MockProjectHistoryApi.addOldFile( - this.project_id, - 42, - 'main.tex', - 'hello world, this is main.tex!' - ) - return this.owner.request( - { - method: 'POST', - url: `/project/${this.project_id}/restore_file`, - json: { - pathname: 'main.tex', - version: 42, - }, - }, - (error, response, body) => { - if (error != null) { - throw error - } - expect(response.statusCode).to.equal(200) - return done() - } - ) - }) - - it('should have created the doc in the root folder', function (done) { - return this.owner.getProject(this.project_id, (error, project) => { - if (error != null) { - throw error - } - let doc = _.find(project.rootFolder[0].docs, doc => - doc.name.match(/main \(Restored on/) - ) - expect(doc).to.exist - doc = MockDocstoreApi.docs[this.project_id][doc._id] - expect(doc.lines).to.deep.equal(['hello world, this is main.tex!']) - return done() - }) - }) - }) - }) }) diff --git a/services/web/test/acceptance/src/SettingsTests.js b/services/web/test/acceptance/src/SettingsTests.js index 1ba2cce2e1..b756050feb 100644 --- a/services/web/test/acceptance/src/SettingsTests.js +++ b/services/web/test/acceptance/src/SettingsTests.js @@ -11,36 +11,14 @@ const { expect } = require('chai') const async = require('async') const User = require('./helpers/User') -const MockV1ApiClass = require('./mocks/MockV1Api') - -let MockV1Api - -before(function () { - MockV1Api = MockV1ApiClass.instance() -}) describe('SettingsPage', function () { beforeEach(function (done) { this.user = new User() - this.v1Id = 1234 - this.v1User = { - id: this.v1Id, - email: this.user.email, - password: this.user.password, - profile: { - id: this.v1Id, - email: this.user.email, - }, - } return async.series( [ this.user.ensureUserExists.bind(this.user), this.user.login.bind(this.user), - cb => this.user.mongoUpdate({ $set: { 'overleaf.id': this.v1Id } }, cb), - cb => { - MockV1Api.setUser(this.v1Id, this.v1User) - return cb() - }, ], done ) @@ -65,24 +43,4 @@ describe('SettingsPage', function () { }) }) }) - - describe('with third-party-references configured', function () { - beforeEach(function injectThirdPartyReferencesEntryIntoDb(done) { - this.user.mongoUpdate( - { $set: { refProviders: { zotero: { encrypted: '2020.9:SNIP' } } } }, - done - ) - }) - - it('should be able to update settings', function (done) { - const newName = 'third-party-references' - this.user.updateSettings({ first_name: newName }, error => { - expect(error).not.to.exist - this.user.get((error, user) => { - user.first_name.should.equal(newName) - done() - }) - }) - }) - }) }) diff --git a/services/web/test/acceptance/src/SubscriptionDashboardTests.js b/services/web/test/acceptance/src/SubscriptionDashboardTests.js deleted file mode 100644 index 0538e339fa..0000000000 --- a/services/web/test/acceptance/src/SubscriptionDashboardTests.js +++ /dev/null @@ -1,516 +0,0 @@ -const { expect } = require('chai') -const async = require('async') -const UserHelper = require('./helpers/UserHelper') -const { Subscription } = require('../../../app/src/models/Subscription') -const { Institution } = require('../../../app/src/models/Institution') -const SubscriptionViewModelBuilder = require('../../../app/src/Features/Subscription/SubscriptionViewModelBuilder') -const RecurlySubscription = require('./helpers/RecurlySubscription') -const MockRecurlyApiClass = require('./mocks/MockRecurlyApi') -const MockV1ApiClass = require('./mocks/MockV1Api') - -async function buildUsersSubscriptionViewModelPromise(userId) { - return new Promise((resolve, reject) => { - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - userId, - (error, data) => { - if (error) reject(error) - resolve(data) - } - ) - }) -} - -let MockV1Api, MockRecurlyApi - -before(function () { - MockV1Api = MockV1ApiClass.instance() - MockRecurlyApi = MockRecurlyApiClass.instance() -}) - -describe('Subscriptions', function () { - describe('dashboard', function () { - let userHelper - beforeEach(async function () { - userHelper = await UserHelper.createUser() - this.user = userHelper.user - }) - - it('should not list personal plan', function () { - const plans = SubscriptionViewModelBuilder.buildPlansList() - expect(plans.individualMonthlyPlans).to.be.a('Array') - const personalMonthlyPlan = plans.individualMonthlyPlans.find( - plan => plan.planCode === 'personal' - ) - expect(personalMonthlyPlan).to.be.undefined - }) - - describe('when the user has no subscription', function () { - beforeEach(function (done) { - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - (error, data) => { - this.data = data - if (error) { - return done(error) - } - done() - } - ) - }) - - it('should return no personalSubscription', function () { - expect(this.data.personalSubscription).to.equal(null) - }) - - it('should return no memberGroupSubscriptions', function () { - expect(this.data.memberGroupSubscriptions).to.deep.equal([]) - }) - }) - - describe('when the user has a subscription with recurly', function () { - beforeEach(function (done) { - this.recurlySubscription = new RecurlySubscription({ - adminId: this.user._id, - planCode: 'collaborator', - tax_in_cents: 100, - tax_rate: 0.2, - unit_amount_in_cents: 500, - currency: 'GBP', - current_period_ends_at: new Date(2018, 4, 5), - state: 'active', - trial_ends_at: new Date(2018, 6, 7), - account: { - hosted_login_token: 'mock-login-token', - email: 'mock@email.com', - }, - }) - MockRecurlyApi.coupons = this.coupons = { - 'test-coupon-1': { description: 'Test Coupon 1' }, - 'test-coupon-2': { description: 'Test Coupon 2' }, - 'test-coupon-3': { name: 'TestCoupon3' }, - } - this.recurlySubscription.ensureExists(error => { - if (error) { - return done(error) - } - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - (error, data) => { - this.data = data - if (error) { - return done(error) - } - done() - } - ) - }) - }) - - after(function (done) { - MockRecurlyApi.mockSubscriptions = [] - MockRecurlyApi.coupons = {} - MockRecurlyApi.redemptions = {} - Subscription.deleteOne( - { - admin_id: this.user._id, - }, - done - ) - }) - - it('should return a personalSubscription with populated recurly data', function () { - const subscription = this.data.personalSubscription - expect(subscription).to.exist - expect(subscription.planCode).to.equal('collaborator') - expect(subscription.recurly).to.exist - expect(subscription.recurly).to.deep.equal({ - activeCoupons: [], - billingDetailsLink: - 'https://test.recurly.com/account/billing_info/edit?ht=mock-login-token', - accountManagementLink: - 'https://test.recurly.com/account/mock-login-token', - currency: 'GBP', - nextPaymentDueAt: '5th May 2018', - price: '£6.00', - state: 'active', - tax: 100, - taxRate: 0.2, - trial_ends_at: new Date(2018, 6, 7), - trialEndsAtFormatted: '7th July 2018', - account: { - account_code: this.user._id.toString(), - email: 'mock@email.com', - hosted_login_token: 'mock-login-token', - }, - additionalLicenses: 0, - totalLicenses: 0, - }) - }) - - it('should return no memberGroupSubscriptions', function () { - expect(this.data.memberGroupSubscriptions).to.deep.equal([]) - }) - - it('should include redeemed coupons', function (done) { - MockRecurlyApi.redemptions[this.user._id] = [ - { state: 'active', coupon_code: 'test-coupon-1' }, - { state: 'inactive', coupon_code: 'test-coupon-2' }, - { state: 'active', coupon_code: 'test-coupon-3' }, - ] - - // rebuild the view model with the redemptions - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - (error, data) => { - expect(error).to.not.exist - expect( - data.personalSubscription.recurly.activeCoupons - ).to.deep.equal([ - { - coupon_code: 'test-coupon-1', - name: '', - description: 'Test Coupon 1', - }, - { - coupon_code: 'test-coupon-3', - name: 'TestCoupon3', - description: '', - }, - ]) - done() - } - ) - }) - - it('should return Recurly account email', function () { - expect(this.data.personalSubscription.recurly.account.email).to.equal( - 'mock@email.com' - ) - }) - }) - - describe('when the user has a subscription without recurly', function () { - beforeEach(function (done) { - Subscription.create( - { - admin_id: this.user._id, - manager_ids: [this.user._id], - planCode: 'collaborator', - }, - error => { - if (error) { - return done(error) - } - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - (error, data) => { - this.data = data - if (error) { - return done(error) - } - done() - } - ) - } - ) - }) - - after(function (done) { - Subscription.deleteOne( - { - admin_id: this.user._id, - }, - done - ) - }) - - it('should return a personalSubscription with no recurly data', function () { - const subscription = this.data.personalSubscription - expect(subscription).to.exist - expect(subscription.planCode).to.equal('collaborator') - expect(subscription.recurly).to.not.exist - }) - - it('should return no memberGroupSubscriptions', function () { - expect(this.data.memberGroupSubscriptions).to.deep.equal([]) - }) - }) - - describe('when the user is a member of a group subscription', function () { - beforeEach(async function () { - const userHelperOwner1 = await UserHelper.createUser() - const userHelperOwner2 = await UserHelper.createUser() - this.owner1 = userHelperOwner1.user - this.owner2 = userHelperOwner2.user - - await Subscription.create({ - admin_id: this.owner1._id, - manager_ids: [this.owner1._id], - planCode: 'collaborator', - groupPlan: true, - member_ids: [this.user._id], - }) - await Subscription.create({ - admin_id: this.owner2._id, - manager_ids: [this.owner2._id], - planCode: 'collaborator', - groupPlan: true, - member_ids: [this.user._id], - }) - this.data = await buildUsersSubscriptionViewModelPromise(this.user._id) - }) - - after(function (done) { - Subscription.deleteOne( - { - admin_id: this.owner1._id, - }, - error => { - if (error) { - return done(error) - } - Subscription.deleteOne( - { - admin_id: this.owner2._id, - }, - done - ) - } - ) - }) - - it('should return no personalSubscription', function () { - expect(this.data.personalSubscription).to.equal(null) - }) - - it('should return the two memberGroupSubscriptions', function () { - expect(this.data.memberGroupSubscriptions.length).to.equal(2) - expect( - // Mongoose populates the admin_id with the user - this.data.memberGroupSubscriptions[0].admin_id._id.toString() - ).to.equal(this.owner1._id.toString()) - expect( - this.data.memberGroupSubscriptions[1].admin_id._id.toString() - ).to.equal(this.owner2._id.toString()) - }) - }) - - describe('when the user is a manager of a group subscription', function () { - beforeEach(async function () { - const userHelperOwner1 = await UserHelper.createUser() - const userHelperOwner2 = await UserHelper.createUser() - this.owner1 = userHelperOwner1.user - this.owner2 = userHelperOwner2.user - - await Subscription.create({ - admin_id: this.owner1._id, - manager_ids: [this.owner1._id, this.user._id], - planCode: 'collaborator', - groupPlan: true, - }) - this.data = await buildUsersSubscriptionViewModelPromise(this.user._id) - }) - - after(function (done) { - Subscription.deleteOne( - { - admin_id: this.owner1._id, - }, - done - ) - }) - - it('should return no personalSubscription', function () { - expect(this.data.personalSubscription).to.equal(null) - }) - - it('should return the managedGroupSubscriptions', function () { - expect(this.data.managedGroupSubscriptions.length).to.equal(1) - const subscription = this.data.managedGroupSubscriptions[0] - expect( - // Mongoose populates the admin_id with the user - subscription.admin_id._id.toString() - ).to.equal(this.owner1._id.toString()) - expect(subscription.groupPlan).to.equal(true) - }) - }) - - describe('when the user is a manager of an institution', function () { - beforeEach(function (done) { - this.v1Id = MockV1Api.nextV1Id() - async.series( - [ - cb => { - Institution.create( - { - v1Id: this.v1Id, - managerIds: [this.user._id], - }, - cb - ) - }, - ], - error => { - if (error) { - return done(error) - } - SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( - this.user, - (error, data) => { - this.data = data - if (error) { - return done(error) - } - done() - } - ) - } - ) - }) - - after(function (done) { - Institution.deleteOne( - { - v1Id: this.v1Id, - }, - done - ) - }) - - it('should return the managedInstitutions', function () { - expect(this.data.managedInstitutions.length).to.equal(1) - const institution = this.data.managedInstitutions[0] - expect(institution.v1Id).to.equal(this.v1Id) - expect(institution.name).to.equal(`Institution ${this.v1Id}`) - }) - }) - - describe('when the user is a member of an affiliation', function () { - beforeEach(async function () { - const v1Id = MockV1Api.nextV1Id() - MockV1Api.setUser(v1Id, { - subscription: {}, - subscription_status: {}, - }) - await UserHelper.updateUser(this.user._id, { - $set: { overleaf: { id: v1Id } }, - }) - - const harvardDomain = 'harvard.example.edu' - const mitDomain = 'mit.example.edu' - const stanfordDomain = 'stanford.example.edu' - const harvardId = MockV1Api.createInstitution({ - name: 'Harvard', - hostname: harvardDomain, - }) - const mitId = MockV1Api.createInstitution({ - name: 'MIT', - hostname: mitDomain, - }) - const stanfordId = MockV1Api.createInstitution({ - name: 'Stanford', - hostname: stanfordDomain, - }) - MockV1Api.updateInstitutionDomain(harvardId, harvardDomain, { - confirmed: true, - }) - MockV1Api.updateInstitutionDomain(mitId, mitDomain, { - confirmed: false, - }) - MockV1Api.updateInstitutionDomain(stanfordId, stanfordDomain, { - confirmed: true, - }) - this.harvardEmail = `unconfirmed-affiliation-email@${harvardDomain}` - this.stanfordEmail = `confirmed-affiliation-email@${stanfordDomain}` - const mitEmail = `confirmed-affiliation-email@${mitDomain}` - userHelper = await UserHelper.loginUser( - userHelper.getDefaultEmailPassword() - ) - await userHelper.addEmail(this.harvardEmail) - await userHelper.addEmailAndConfirm(this.user._id, this.stanfordEmail) - await userHelper.addEmailAndConfirm(this.user._id, mitEmail) - this.data = await buildUsersSubscriptionViewModelPromise(this.user._id) - }) - - it('should return only the affilations with confirmed institutions, and confirmed emails', function () { - expect(this.data.confirmedMemberAffiliations.length).to.equal(1) - expect( - this.data.confirmedMemberAffiliations[0].institution.name - ).to.equal('Stanford') - }) - }) - - describe('when the user has a v1 subscription', function () { - beforeEach(async function () { - let v1Id - MockV1Api.setUser((v1Id = MockV1Api.nextV1Id()), { - subscription: (this.subscription = { - trial: false, - has_plan: true, - teams: [ - { - id: 56, - name: 'Test team', - }, - ], - }), - subscription_status: (this.subscription_status = { - product: { mock: 'product' }, - team: null, - }), - }) - await UserHelper.updateUser(this.user._id, { - $set: { - overleaf: { - id: v1Id, - }, - }, - }) - this.data = await buildUsersSubscriptionViewModelPromise(this.user._id) - }) - - it('should return no personalSubscription', function () { - expect(this.data.personalSubscription).to.equal(null) - }) - - it('should return no memberGroupSubscriptions', function () { - expect(this.data.memberGroupSubscriptions).to.deep.equal([]) - }) - - it('should return a v1SubscriptionStatus', function () { - expect(this.data.v1SubscriptionStatus).to.deep.equal( - this.subscription_status - ) - }) - }) - }) - - describe('canceling', function () { - let userHelper, v1Id - beforeEach(async function () { - v1Id = MockV1Api.nextV1Id() - userHelper = await UserHelper.createUser({ overleaf: { id: v1Id } }) - this.user = userHelper.user - MockV1Api.setUser(v1Id, (this.v1_user = {})) - - userHelper = await UserHelper.loginUser( - userHelper.getDefaultEmailPassword() - ) - this.response = await userHelper.request.post( - '/user/subscription/v1/cancel', - { - simple: false, - } - ) - }) - - it('should tell v1 to cancel the subscription', function () { - expect(this.v1_user.canceled).to.equal(true) - }) - - it('should redirect to the subscription dashboard', function () { - expect(this.response.statusCode).to.equal(302) - expect(this.response.headers.location).to.equal('/user/subscription') - }) - }) -}) diff --git a/services/web/test/acceptance/src/SubscriptionDeletionTests.js b/services/web/test/acceptance/src/SubscriptionDeletionTests.js deleted file mode 100644 index fe4f65c0e0..0000000000 --- a/services/web/test/acceptance/src/SubscriptionDeletionTests.js +++ /dev/null @@ -1,112 +0,0 @@ -const { expect } = require('chai') -const async = require('async') -const request = require('./helpers/request') -const User = require('./helpers/User') -const RecurlySubscription = require('./helpers/RecurlySubscription') -const SubscriptionUpdater = require('../../../app/src/Features/Subscription/SubscriptionUpdater') -const Settings = require('@overleaf/settings') - -describe('Subscriptions', function () { - describe('deletion', function () { - beforeEach(function (done) { - this.adminUser = new User() - this.memberUser = new User() - this.auth = { - user: Settings.apis.recurly.webhookUser, - pass: Settings.apis.recurly.webhookPass, - sendImmediately: true, - } - - async.series( - [ - cb => this.adminUser.ensureUserExists(cb), - cb => this.memberUser.ensureUserExists(cb), - cb => { - this.recurlySubscription = new RecurlySubscription({ - adminId: this.adminUser._id, - memberIds: [this.memberUser._id], - invitedEmails: ['foo@bar.com'], - teamInvites: [{ email: 'foo@baz.com' }], - groupPlan: true, - state: 'expired', - planCode: 'professional', - }) - this.subscription = this.recurlySubscription.subscription - this.recurlySubscription.ensureExists(cb) - }, - cb => this.subscription.refreshUsersFeatures(cb), - ], - done - ) - }) - - it('should not allow unauthorized access to the Recurly callback', function (done) { - const url = '/user/subscription/callback' - const body = this.recurlySubscription.buildCallbackXml() - - request.post({ url, body }, (error, { statusCode }) => { - if (error) { - return done(error) - } - expect(statusCode).to.equal(401) - done() - }) - }) - - it('deletes via Recurly callback', function (done) { - const url = '/user/subscription/callback' - const body = this.recurlySubscription.buildCallbackXml() - - request.post({ url, body, auth: this.auth }, (error, { statusCode }) => { - if (error) { - return done(error) - } - expect(statusCode).to.equal(200) - this.subscription.expectDeleted({ ip: '127.0.0.1' }, done) - }) - }) - - it('refresh features', function (done) { - const url = '/user/subscription/callback' - const body = this.recurlySubscription.buildCallbackXml() - - request.post({ url, body, auth: this.auth }, (error, { statusCode }) => { - if (error) { - return done(error) - } - this.memberUser.getFeatures((error, features) => { - expect(features.collaborators).to.equal(1) - done(error) - }) - }) - }) - - it('allows deletion when deletedSubscription exists', function (done) { - const url = '/user/subscription/callback' - const body = this.recurlySubscription.buildCallbackXml() - - // create fake deletedSubscription - SubscriptionUpdater.createDeletedSubscription( - this.subscription, - {}, - error => { - if (error) { - return done(error) - } - - // try deleting the subscription - request.post( - { url, body, auth: this.auth }, - (error, { statusCode }) => { - if (error) { - return done(error) - } - expect(statusCode).to.equal(200) - this.subscription.expectDeleted({ ip: '127.0.0.1' }, done) - } - ) - } - ) - }) - }) -}) diff --git a/services/web/test/acceptance/src/SubscriptionFeaturesTests.js b/services/web/test/acceptance/src/SubscriptionFeaturesTests.js deleted file mode 100644 index 351b0889b6..0000000000 --- a/services/web/test/acceptance/src/SubscriptionFeaturesTests.js +++ /dev/null @@ -1,74 +0,0 @@ -const { expect } = require('chai') -const async = require('async') -const User = require('./helpers/User') -const Subscription = require('./helpers/Subscription') - -describe('Subscriptions', function () { - describe('features', function () { - describe('individual subscriptions', function () { - beforeEach(function (done) { - this.adminUser = new User() - async.series( - [ - cb => this.adminUser.ensureUserExists(cb), - cb => { - this.subscription = new Subscription({ - adminId: this.adminUser._id, - groupPlan: false, - planCode: 'professional', - }) - this.subscription.ensureExists(cb) - }, - cb => this.subscription.refreshUsersFeatures(cb), - ], - done - ) - }) - - it('should give features to admin', function (done) { - this.adminUser.getFeatures((error, features) => { - expect(features.collaborators).to.equal(-1) - done(error) - }) - }) - }) - - describe('group subscriptions', function () { - beforeEach(function (done) { - this.adminUser = new User() - this.memberUser = new User() - async.series( - [ - cb => this.adminUser.ensureUserExists(cb), - cb => this.memberUser.ensureUserExists(cb), - cb => { - this.subscription = new Subscription({ - adminId: this.adminUser._id, - memberIds: [this.memberUser._id], - groupPlan: true, - planCode: 'professional', - }) - this.subscription.ensureExists(cb) - }, - cb => this.subscription.refreshUsersFeatures(cb), - ], - done - ) - }) - - it('should give features to member', function (done) { - this.memberUser.getFeatures((error, features) => { - expect(features.collaborators).to.equal(-1) - done(error) - }) - }) - - it('should not give features to admin', function (done) { - this.adminUser.getFeatures((error, features) => { - expect(features.collaborators).to.equal(1) - done(error) - }) - }) - }) - }) -}) diff --git a/services/web/test/acceptance/src/TokenAccessTests.js b/services/web/test/acceptance/src/TokenAccessTests.js index 1dcfa2d487..9b3cfbb0c5 100644 --- a/services/web/test/acceptance/src/TokenAccessTests.js +++ b/services/web/test/acceptance/src/TokenAccessTests.js @@ -3,16 +3,9 @@ const async = require('async') const User = require('./helpers/User') const request = require('./helpers/request') const settings = require('@overleaf/settings') -const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb') -const MockV1ApiClass = require('./mocks/MockV1Api') +const { db } = require('../../../app/src/infrastructure/mongodb') const expectErrorResponse = require('./helpers/expectErrorResponse') -let MockV1Api - -before(function () { - MockV1Api = MockV1ApiClass.instance() -}) - const tryEditorAccess = (user, projectId, test, callback) => async.series( [ @@ -1213,7 +1206,6 @@ describe('TokenAccess', function () { describe('deleted project', function () { beforeEach(function (done) { - settings.overleaf = { host: 'http://localhost:5000' } this.owner.createProject( `delete-test${Math.random()}`, (err, projectId) => { @@ -1237,10 +1229,6 @@ describe('TokenAccess', function () { ) }) - afterEach(function () { - delete settings.overleaf - }) - it('should 404', function (done) { async.series( [ @@ -1281,258 +1269,4 @@ describe('TokenAccess', function () { ) }) }) - - describe('unimported v1 project', function () { - beforeEach(function () { - settings.overleaf = { host: 'http://localhost:5000' } - }) - - afterEach(function () { - delete settings.overleaf - }) - - it('should show download option for read and write token', function (done) { - const unimportedV1Token = '123abcdefabcdef' - const docInfo = { - exists: true, - exported: false, - has_owner: true, - name: 'test', - } - MockV1Api.setDocInfo(unimportedV1Token, docInfo) - tryReadAndWriteTokenAccess( - this.owner, - unimportedV1Token, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(200) - expect(body).to.deep.equal({ - v1Import: { - hasOwner: true, - name: 'test', - projectId: unimportedV1Token, - status: 'canDownloadZip', - }, - }) - }, - done - ) - }) - - it('should show download option for read only token to v1', function (done) { - const unimportedV1Token = 'aaaaaabbbbbb' - const docInfo = { - exists: true, - exported: false, - has_owner: true, - name: 'test', - } - MockV1Api.setDocInfo(unimportedV1Token, docInfo) - tryReadOnlyTokenAccess( - this.owner, - unimportedV1Token, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(200) - expect(body).to.deep.equal({ - v1Import: { - hasOwner: true, - name: 'test', - projectId: unimportedV1Token, - status: 'canDownloadZip', - }, - }) - }, - done - ) - }) - }) - - describe('importing v1 project', function () { - beforeEach(function (done) { - settings.projectImportingCheckMaxCreateDelta = 3600 - settings.overleaf = { host: 'http://localhost:5000' } - this.owner.createProject( - `token-rw-test${Math.random()}`, - (err, projectId) => { - if (err != null) { - return done(err) - } - this.projectId = projectId - db.users.updateOne( - { _id: ObjectId(this.owner._id.toString()) }, - { $set: { 'overleaf.id': 321321 } }, - err => { - if (err) { - return done(err) - } - this.owner.makeTokenBased(this.projectId, err => { - if (err != null) { - return done(err) - } - db.projects.updateOne( - { _id: ObjectId(projectId) }, - { $set: { overleaf: { id: 1234 } } }, - err => { - if (err != null) { - return done(err) - } - this.owner.getProject(this.projectId, (err, project) => { - if (err != null) { - return done(err) - } - this.tokens = project.tokens - const docInfo = { - exists: true, - exported: false, - has_owner: true, - name: 'Test Project Import Example', - } - MockV1Api.setDocInfo(this.tokens.readAndWrite, docInfo) - MockV1Api.setDocInfo(this.tokens.readOnly, docInfo) - db.projects.deleteOne({ _id: ObjectId(projectId) }, done) - }) - } - ) - }) - } - ) - } - ) - }) - - afterEach(function () { - delete settings.projectImportingCheckMaxCreateDelta - delete settings.overleaf - }) - - it('should show importing page for read, and read-write tokens', function (done) { - async.series( - [ - cb => - tryReadAndWriteTokenAccess( - this.owner, - this.tokens.readAndWrite, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(200) - expect(body).to.deep.equal({ - v1Import: { - status: 'canDownloadZip', - projectId: this.tokens.readAndWrite, - hasOwner: true, - name: 'Test Project Import Example', - }, - }) - }, - cb - ), - cb => - tryReadOnlyTokenAccess( - this.owner, - this.tokens.readOnly, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(200) - expect(body).to.deep.equal({ - v1Import: { - status: 'canDownloadZip', - projectId: this.tokens.readOnly, - hasOwner: true, - name: 'Test Project Import Example', - }, - }) - }, - cb - ), - cb => - tryEditorAccess( - this.owner, - this.projectId, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - cb => - tryContentAccess( - this.other2, - this.projectId, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - ], - done - ) - }) - - describe('when the v1 doc does not exist', function (done) { - beforeEach(function (done) { - const docInfo = null - MockV1Api.setDocInfo(this.tokens.readAndWrite, docInfo) - MockV1Api.setDocInfo(this.tokens.readOnly, docInfo) - done() - }) - - it('should get a 404 response on the post endpoint', function (done) { - async.series( - [ - cb => - tryReadAndWriteTokenAccess( - this.owner, - this.tokens.readAndWrite, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - cb => - tryReadOnlyTokenAccess( - this.owner, - this.tokens.readOnly, - (response, body) => { - expect(response.statusCode).to.equal(200) - }, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - cb => - tryEditorAccess( - this.owner, - this.projectId, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - cb => - tryContentAccess( - this.other2, - this.projectId, - (response, body) => { - expect(response.statusCode).to.equal(404) - }, - cb - ), - ], - done - ) - }) - }) - }) }) diff --git a/services/web/test/acceptance/src/UserEmailsTests.js b/services/web/test/acceptance/src/UserEmailsTests.js deleted file mode 100644 index 806907a5cb..0000000000 --- a/services/web/test/acceptance/src/UserEmailsTests.js +++ /dev/null @@ -1,1188 +0,0 @@ -const { expect } = require('chai') -const async = require('async') -const moment = require('moment') -const Features = require('../../../app/src/infrastructure/Features') -const User = require('./helpers/User') -const UserHelper = require('./helpers/UserHelper') -const UserUpdater = require('../../../app/src/Features/User/UserUpdater') -const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb') -const MockV1ApiClass = require('./mocks/MockV1Api') -const expectErrorResponse = require('./helpers/expectErrorResponse') - -let MockV1Api - -before(function () { - MockV1Api = MockV1ApiClass.instance() -}) - -describe('UserEmails', function () { - beforeEach(function (done) { - this.timeout(20000) - this.user = new User() - this.user.login(done) - }) - - describe('confirming an email', function () { - it('should confirm the email', function (done) { - let token = null - async.series( - [ - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'newly-added-email@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - this.user.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - expect(body[0].confirmedAt).to.not.exist - expect(body[0].reconfirmedAt).to.not.exist - expect(body[1].confirmedAt).to.not.exist - expect(body[1].reconfirmedAt).to.not.exist - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should only be one confirmation token at the moment - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal( - 'newly-added-email@example.com' - ) - expect(tokens[0].data.user_id).to.equal(this.user._id) - ;({ token } = tokens[0]) - cb() - }) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/confirm', - json: { - token, - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - this.user.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - expect(body[0].confirmedAt).to.not.exist - expect(body[0].reconfirmedAt).to.not.exist - expect(body[1].confirmedAt).to.exist - expect(body[1].reconfirmedAt).to.exist - expect(body[1].reconfirmedAt).to.deep.equal(body[1].confirmedAt) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // Token should be deleted after use - expect(tokens.length).to.equal(0) - cb() - }) - }, - ], - done - ) - }) - - it('should not allow confirmation of the email if the user has changed', function (done) { - let token1 = null - let token2 = null - this.user2 = new User() - this.email = 'duplicate-email@example.com' - async.series( - [ - cb => this.user2.login(cb), - cb => { - // Create email for first user - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { email: this.email }, - }, - cb - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should only be one confirmation token at the moment - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal(this.email) - expect(tokens[0].data.user_id).to.equal(this.user._id) - token1 = tokens[0].token - cb() - }) - }, - cb => { - // Delete the email from the first user - this.user.request( - { - method: 'POST', - url: '/user/emails/delete', - json: { email: this.email }, - }, - cb - ) - }, - cb => { - // Create email for second user - this.user2.request( - { - method: 'POST', - url: '/user/emails', - json: { email: this.email }, - }, - cb - ) - }, - cb => { - // Original confirmation token should no longer work - this.user.request( - { - method: 'POST', - url: '/user/emails/confirm', - json: { - token: token1, - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(404) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user2._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // The first token has been used, so this should be token2 now - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal(this.email) - expect(tokens[0].data.user_id).to.equal(this.user2._id) - token2 = tokens[0].token - cb() - }) - }, - cb => { - // Second user should be able to confirm the email - this.user2.request( - { - method: 'POST', - url: '/user/emails/confirm', - json: { - token: token2, - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - this.user2.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - expect(body[0].confirmedAt).to.not.exist - expect(body[1].confirmedAt).to.exist - cb() - } - ) - }, - ], - done - ) - }) - }) - - describe('reconfirm an email', function () { - let email, userHelper, confirmedAtDate - beforeEach(async function () { - userHelper = new UserHelper() - email = userHelper.getDefaultEmail() - userHelper = await UserHelper.createUser({ email }) - userHelper = await UserHelper.loginUser({ - email, - password: userHelper.getDefaultPassword(), - }) - // original confirmation - await userHelper.confirmEmail(userHelper.user._id, email) - const user = (await UserHelper.getUser({ email })).user - confirmedAtDate = user.emails[0].confirmedAt - expect(user.emails[0].confirmedAt).to.exist - expect(user.emails[0].reconfirmedAt).to.exist - }) - it('should set reconfirmedAt and not reset confirmedAt', async function () { - await userHelper.confirmEmail(userHelper.user._id, email) - const user = (await UserHelper.getUser({ email })).user - expect(user.emails[0].confirmedAt).to.exist - expect(user.emails[0].reconfirmedAt).to.exist - expect(user.emails[0].confirmedAt).to.deep.equal(confirmedAtDate) - expect(user.emails[0].reconfirmedAt).to.not.deep.equal( - user.emails[0].confirmedAt - ) - expect(user.emails[0].reconfirmedAt > user.emails[0].confirmedAt).to.be - .true - }) - }) - - describe('with an expired token', function () { - it('should not confirm the email', function (done) { - let token = null - async.series( - [ - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: (this.email = 'expired-token-email@example.com'), - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should only be one confirmation token at the moment - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal(this.email) - expect(tokens[0].data.user_id).to.equal(this.user._id) - ;({ token } = tokens[0]) - cb() - }) - }, - cb => { - db.tokens.update( - { - token, - }, - { - $set: { - expiresAt: new Date(Date.now() - 1000000), - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/confirm', - json: { - token, - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(404) - cb() - } - ) - }, - ], - done - ) - }) - }) - - describe('resending the confirmation', function () { - it('should generate a new token', function (done) { - async.series( - [ - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'reconfirmation-email@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should only be one confirmation token at the moment - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal( - 'reconfirmation-email@example.com' - ) - expect(tokens[0].data.user_id).to.equal(this.user._id) - cb() - }) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/resend_confirmation', - json: { - email: 'reconfirmation-email@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should be two tokens now - expect(tokens.length).to.equal(2) - expect(tokens[0].data.email).to.equal( - 'reconfirmation-email@example.com' - ) - expect(tokens[0].data.user_id).to.equal(this.user._id) - expect(tokens[1].data.email).to.equal( - 'reconfirmation-email@example.com' - ) - expect(tokens[1].data.user_id).to.equal(this.user._id) - cb() - }) - }, - ], - done - ) - }) - - it('should create a new token if none exists', function (done) { - // This should only be for users that have sign up with their main - // emails before the confirmation system existed - async.series( - [ - cb => { - db.tokens.remove( - { - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/resend_confirmation', - json: { - email: this.user.email, - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - // There should still only be one confirmation token - expect(tokens.length).to.equal(1) - expect(tokens[0].data.email).to.equal(this.user.email) - expect(tokens[0].data.user_id).to.equal(this.user._id) - cb() - }) - }, - ], - done - ) - }) - - it("should not allow reconfirmation if the email doesn't match the user", function (done) { - async.series( - [ - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/resend_confirmation', - json: { - email: 'non-matching-email@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(422) - cb() - } - ) - }, - cb => { - db.tokens - .find({ - use: 'email_confirmation', - 'data.user_id': this.user._id, - usedAt: { $exists: false }, - }) - .toArray((error, tokens) => { - expect(error).to.not.exist - expect(tokens.length).to.equal(0) - cb() - }) - }, - ], - done - ) - }) - }) - - describe('setting a default email', function () { - it('should update confirmed emails for users not in v1', function (done) { - async.series( - [ - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'new-confirmed-default@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - // Mark the email as confirmed - db.users.updateOne( - { - 'emails.email': 'new-confirmed-default@example.com', - }, - { - $set: { - 'emails.$.confirmedAt': new Date(), - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/default', - json: { - email: 'new-confirmed-default@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - this.user.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - expect(body[0].confirmedAt).to.not.exist - expect(body[0].default).to.equal(false) - expect(body[1].confirmedAt).to.exist - expect(body[1].default).to.equal(true) - cb() - } - ) - }, - ], - done - ) - }) - - it('should not allow changing unconfirmed emails in v1', function (done) { - async.series( - [ - cb => { - db.users.updateOne( - { - _id: ObjectId(this.user._id), - }, - { - $set: { - 'overleaf.id': 42, - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'new-unconfirmed-default@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/default', - json: { - email: 'new-unconfirmed-default@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(409) - cb() - } - ) - }, - cb => { - this.user.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(body[0].default).to.equal(true) - expect(body[1].default).to.equal(false) - cb() - } - ) - }, - ], - done - ) - }) - - it('should not update the email in v1', function (done) { - async.series( - [ - cb => { - db.users.updateOne( - { - _id: ObjectId(this.user._id), - }, - { - $set: { - 'overleaf.id': 42, - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'new-confirmed-default-in-v1@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - // Mark the email as confirmed - db.users.updateOne( - { - 'emails.email': 'new-confirmed-default-in-v1@example.com', - }, - { - $set: { - 'emails.$.confirmedAt': new Date(), - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/default', - json: { - email: 'new-confirmed-default-in-v1@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - ], - error => { - expect(error).to.not.exist - expect(MockV1Api.updateEmail.callCount).to.equal(0) - done() - } - ) - }) - - it('should not return an error if the email exists in v1', function (done) { - MockV1Api.existingEmails.push('exists-in-v1@example.com') - async.series( - [ - cb => { - db.users.updateOne( - { - _id: ObjectId(this.user._id), - }, - { - $set: { - 'overleaf.id': 42, - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'exists-in-v1@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - cb() - } - ) - }, - cb => { - // Mark the email as confirmed - db.users.updateOne( - { - 'emails.email': 'exists-in-v1@example.com', - }, - { - $set: { - 'emails.$.confirmedAt': new Date(), - }, - }, - cb - ) - }, - cb => { - this.user.request( - { - method: 'POST', - url: '/user/emails/default', - json: { - email: 'exists-in-v1@example.com', - }, - }, - (error, response, body) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - cb() - } - ) - }, - cb => { - this.user.request( - { url: '/user/emails', json: true }, - (error, response, body) => { - expect(error).to.not.exist - expect(body[0].default).to.equal(false) - expect(body[1].default).to.equal(true) - cb() - } - ) - }, - ], - done - ) - }) - - describe('audit log', function () { - const originalEmail = 'original@overleaf.com' - let otherEmail, response, userHelper, user, userId - beforeEach(async function () { - otherEmail = 'other@overleaf.com' - userHelper = new UserHelper() - userHelper = await UserHelper.createUser({ - email: originalEmail, - }) - userHelper = await UserHelper.loginUser({ - email: originalEmail, - password: userHelper.getDefaultPassword(), - }) - userId = userHelper.user._id - response = await userHelper.request.post({ - form: { - email: otherEmail, - }, - simple: false, - uri: '/user/emails', - }) - expect(response.statusCode).to.equal(204) - const token = ( - await db.tokens.findOne({ - 'data.user_id': userId.toString(), - 'data.email': otherEmail, - }) - ).token - response = await userHelper.request.post(`/user/emails/confirm`, { - form: { - token, - }, - simple: false, - }) - expect(response.statusCode).to.equal(200) - response = await userHelper.request.post('/user/emails/default', { - form: { - email: otherEmail, - }, - simple: false, - }) - expect(response.statusCode).to.equal(200) - userHelper = await UserHelper.getUser(userId) - user = userHelper.user - }) - it('should be updated', function () { - const auditLog = userHelper.getAuditLogWithoutNoise() - const entry = auditLog[auditLog.length - 1] - expect(typeof entry.initiatorId).to.equal('object') - expect(entry.initiatorId).to.deep.equal(user._id) - expect(entry.ipAddress).to.equal('127.0.0.1') - expect(entry.info).to.deep.equal({ - newPrimaryEmail: otherEmail, - oldPrimaryEmail: originalEmail, - }) - }) - }) - - describe('session cleanup', function () { - beforeEach(function setupSecondSession(done) { - this.userSession2 = new User() - this.userSession2.email = this.user.email - this.userSession2.emails = this.user.emails - this.userSession2.password = this.user.password - // login before adding the new email address - // User.login() performs a mongo update and resets the .emails field. - this.userSession2.login(done) - }) - - beforeEach(function checkSecondSessionLiveness(done) { - this.userSession2.request( - { method: 'GET', url: '/project', followRedirect: false }, - (error, response) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - done() - } - ) - }) - - beforeEach(function addSecondaryEmail(done) { - this.user.request( - { - method: 'POST', - url: '/user/emails', - json: { email: 'new-confirmed-default@example.com' }, - }, - (error, response) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(204) - done() - } - ) - }) - - beforeEach(function confirmSecondaryEmail(done) { - db.users.updateOne( - { 'emails.email': 'new-confirmed-default@example.com' }, - { $set: { 'emails.$.confirmedAt': new Date() } }, - done - ) - }) - - beforeEach(function setDefault(done) { - this.user.request( - { - method: 'POST', - url: '/user/emails/default', - json: { email: 'new-confirmed-default@example.com' }, - }, - (error, response) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(200) - done() - } - ) - }) - - it('should logout the other sessions', function (done) { - this.userSession2.request( - { method: 'GET', url: '/project', followRedirect: false }, - (error, response) => { - expect(error).to.not.exist - expect(response.statusCode).to.equal(302) - expect(response.headers) - .to.have.property('location') - .to.match(/^\/login/) - done() - } - ) - }) - }) - }) - - describe('when not logged in', function () { - beforeEach(function (done) { - this.anonymous = new User() - this.anonymous.getCsrfToken(done) - }) - it('should return a plain 403 when setting the email', function (done) { - this.anonymous.request( - { - method: 'POST', - url: '/user/emails', - json: { - email: 'newly-added-email@example.com', - }, - }, - (error, response, body) => { - if (error) { - return done(error) - } - expectErrorResponse.requireLogin.json(response, body) - done() - } - ) - }) - }) - - describe('secondary email', function () { - let newEmail, userHelper, userId, user - beforeEach(async function () { - newEmail = 'a-new-email@overleaf.com' - userHelper = new UserHelper() - userHelper = await UserHelper.createUser() - userHelper = await UserHelper.loginUser({ - email: userHelper.getDefaultEmail(), - password: userHelper.getDefaultPassword(), - }) - userId = userHelper.user._id - await userHelper.request.post({ - form: { - email: newEmail, - }, - simple: false, - uri: '/user/emails', - }) - userHelper = await UserHelper.getUser(userId) - user = userHelper.user - }) - it('should add the email', async function () { - expect(user.emails[1].email).to.equal(newEmail) - }) - it('should add to the user audit log', async function () { - const auditLog = userHelper.getAuditLogWithoutNoise() - expect(typeof auditLog[0].initiatorId).to.equal('object') - expect(auditLog[0].initiatorId).to.deep.equal(user._id) - expect(auditLog[0].info.newSecondaryEmail).to.equal(newEmail) - expect(auditLog[0].ip).to.equal(this.user.request.ip) - }) - }) - - describe('notification period', function () { - let defaultEmail, userHelper, email1, email2, email3 - const maxConfirmationMonths = 12 - - beforeEach(async function () { - if (!Features.hasFeature('affiliations')) { - this.skip() - } - userHelper = new UserHelper() - defaultEmail = userHelper.getDefaultEmail() - userHelper = await UserHelper.createUser({ email: defaultEmail }) - userHelper = await UserHelper.loginUser({ - email: defaultEmail, - password: userHelper.getDefaultPassword(), - }) - const institutionId = MockV1Api.createInstitution({ - commonsAccount: true, - ssoEnabled: false, - maxConfirmationMonths, - }) - const domain = 'example-affiliation.com' - MockV1Api.addInstitutionDomain(institutionId, domain, { confirmed: true }) - - email1 = `leonard@${domain}` - email2 = `mccoy@${domain}` - email3 = `bones@${domain}` - }) - - describe('non SSO affiliations', function () { - beforeEach(async function () { - // create a user with 3 affiliations at the institution. - // all are within in the notification period - const userId = userHelper.user._id - await userHelper.addEmailAndConfirm(userId, email1) - await userHelper.addEmailAndConfirm(userId, email2) - await userHelper.addEmailAndConfirm(userId, email3) - await userHelper.changeConfirmedToNotificationPeriod( - userId, - email1, - maxConfirmationMonths - ) - await userHelper.changeConfirmedToNotificationPeriod( - userId, - email2, - maxConfirmationMonths - ) - await userHelper.changeConfirmedToPastReconfirmation( - userId, - email3, - maxConfirmationMonths - ) - }) - - describe('when all affiliations in notification period or past reconfirm date', function () { - it('should flag inReconfirmNotificationPeriod for all affiliations in period', async function () { - const response = await userHelper.request.get('/user/emails') - expect(response.statusCode).to.equal(200) - const fullEmails = JSON.parse(response.body) - expect(fullEmails.length).to.equal(4) - expect(fullEmails[0].affiliation).to.not.exist - expect( - fullEmails[1].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[2].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[3].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - }) - - it('should set pastReconfirmDate and emailHasInstitutionLicence:false for lapsed confirmations', async function () { - const response = await userHelper.request.get('/user/emails') - expect(response.statusCode).to.equal(200) - const fullEmails = JSON.parse(response.body) - expect(fullEmails.length).to.equal(4) - expect(fullEmails[0].affiliation).to.not.exist - expect(fullEmails[1].affiliation.pastReconfirmDate).to.equal(false) - expect(fullEmails[1].emailHasInstitutionLicence).to.equal(true) - expect(fullEmails[2].affiliation.pastReconfirmDate).to.equal(false) - expect(fullEmails[2].emailHasInstitutionLicence).to.equal(true) - expect(fullEmails[3].affiliation.pastReconfirmDate).to.equal(true) - expect(fullEmails[3].emailHasInstitutionLicence).to.equal(false) - }) - }) - - describe('should flag emails before their confirmation expires, but within the notification period', function () { - beforeEach(async function () { - const dateInPeriodButNotExpired = moment() - .subtract(maxConfirmationMonths, 'months') - .add(14, 'days') - .toDate() - const backdatedDays = moment().diff(dateInPeriodButNotExpired, 'days') - await userHelper.changeConfirmationDate( - userHelper.user._id, - email1, - backdatedDays - ) - await userHelper.changeConfirmationDate( - userHelper.user._id, - email2, - backdatedDays - ) - await userHelper.changeConfirmationDate( - userHelper.user._id, - email3, - backdatedDays - ) - }) - - it('should flag the emails', async function () { - const response = await userHelper.request.get('/user/emails') - expect(response.statusCode).to.equal(200) - const fullEmails = JSON.parse(response.body) - expect(fullEmails.length).to.equal(4) - expect(fullEmails[0].affiliation).to.not.exist - expect( - fullEmails[1].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[2].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[3].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - // ensure dates are not past reconfirmation period - function _getLastDayToReconfirm(date) { - return moment(date).add(maxConfirmationMonths, 'months') - } - - expect( - moment(fullEmails[1].reconfirmedAt).isAfter( - _getLastDayToReconfirm(fullEmails[1].reconfirmedAt) - ) - ).to.equal(false) - - expect( - moment(fullEmails[2].reconfirmedAt).isAfter( - _getLastDayToReconfirm(fullEmails[2].reconfirmedAt) - ) - ).to.equal(false) - expect( - moment(fullEmails[3].reconfirmedAt).isAfter( - _getLastDayToReconfirm(fullEmails[3].reconfirmedAt) - ) - ).to.equal(false) - }) - }) - - describe('missing reconfirmedAt', function () { - beforeEach(async function () { - const userId = userHelper.user._id - const query = { - _id: userId, - 'emails.email': email2, - } - const update = { - $unset: { 'emails.$.reconfirmedAt': true }, - } - await UserUpdater.promises.updateUser(query, update) - }) - - it('should fallback to confirmedAt for date check', async function () { - const response = await userHelper.request.get('/user/emails') - expect(response.statusCode).to.equal(200) - const fullEmails = JSON.parse(response.body) - expect(fullEmails.length).to.equal(4) - expect(fullEmails[0].affiliation).to.not.exist - expect(fullEmails[2].reconfirmedAt).to.not.exist - expect( - fullEmails[1].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[2].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - expect( - fullEmails[3].affiliation.inReconfirmNotificationPeriod - ).to.equal(true) - }) - }) - }) - }) -}) diff --git a/services/web/test/acceptance/src/UserHelperTests.js b/services/web/test/acceptance/src/UserHelperTests.js index 7dad120090..f3aee82dab 100644 --- a/services/web/test/acceptance/src/UserHelperTests.js +++ b/services/web/test/acceptance/src/UserHelperTests.js @@ -4,9 +4,9 @@ const Features = require('../../../app/src/infrastructure/Features') const { expect } = require('chai') describe('UserHelper', function () { - // Disable all tests unless the public-registration feature is enabled + // Disable all tests unless the registration feature is enabled beforeEach(function () { - if (!Features.hasFeature('public-registration')) { + if (!Features.hasFeature('registration')) { this.skip() } }) diff --git a/services/web/test/acceptance/src/UserThirdPartyIdentityTests.js b/services/web/test/acceptance/src/UserThirdPartyIdentityTests.js deleted file mode 100644 index 20e3e89d56..0000000000 --- a/services/web/test/acceptance/src/UserThirdPartyIdentityTests.js +++ /dev/null @@ -1,229 +0,0 @@ -/* eslint-disable - node/handle-callback-err, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const Errors = require('../../../app/src/Features/Errors/Errors') -const Settings = require('@overleaf/settings') -const User = require('./helpers/User') -const ThirdPartyIdentityManager = require('../../../app/src/Features/User/ThirdPartyIdentityManager') -const { expect } = require('chai') - -describe('ThirdPartyIdentityManager', function () { - beforeEach(function (done) { - this.provider = 'provider' - this.externalUserId = 'external-user-id' - this.externalData = { test: 'data' } - this.user = new User() - this.user.ensureUserExists(done) - }) - - afterEach(function (done) { - return this.user.fullDeleteUser(this.user.email, done) - }) - - describe('login', function () { - describe('when third party identity exists', function () { - beforeEach(function (done) { - return ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - done - ) - }) - - it('should return user', function (done) { - ThirdPartyIdentityManager.login( - this.provider, - this.externalUserId, - this.externalData, - (err, user) => { - expect(err).to.be.null - expect(user._id.toString()).to.equal(this.user.id) - return done() - } - ) - }) - - it('should merge external data', function (done) { - this.externalData = { - test: 'different', - another: 'key', - } - ThirdPartyIdentityManager.login( - this.provider, - this.externalUserId, - this.externalData, - (err, user) => { - expect(err).to.be.null - expect(user.thirdPartyIdentifiers[0].externalData).to.deep.equal( - this.externalData - ) - return done() - } - ) - }) - }) - - describe('when third party identity does not exists', function () { - it('should return error', function (done) { - ThirdPartyIdentityManager.login( - this.provider, - this.externalUserId, - this.externalData, - (err, user) => { - expect(err.name).to.equal('ThirdPartyUserNotFoundError') - return done() - } - ) - }) - }) - }) - - describe('link', function () { - describe('when provider not already linked', function () { - it('should link provider to user', function (done) { - ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, res) => { - expect(res.thirdPartyIdentifiers.length).to.equal(1) - return done() - } - ) - }) - }) - - describe('when provider is already linked', function () { - beforeEach(function (done) { - ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - done - ) - }) - - it('should link provider to user', function (done) { - ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, res) => { - expect(res).to.exist - done() - } - ) - }) - - it('should not create duplicate thirdPartyIdentifiers', function (done) { - ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, user) => { - expect(user.thirdPartyIdentifiers.length).to.equal(1) - return done() - } - ) - }) - - it('should replace existing data', function (done) { - this.externalData = { replace: 'data' } - return ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, user) => { - expect(user.thirdPartyIdentifiers.length).to.equal(1) - return done() - } - ) - }) - - // describe('when another account tries to link same provider/externalUserId', function() { - // NOTE: Cannot run this test because we do not have indexes on the test DB - // beforeEach(function(done) { - // this.user2 = new User() - // this.user2.ensureUserExists(done) - // }) - // it('should not link provider', function(done) { - // ThirdPartyIdentityManager.link( - // this.user2.id, - // this.provider, - // this.externalUserId, - // this.externalData, - // (err, user) => { - // expect(err.name).to.equal('ThirdPartyIdentityExistsError') - // return done() - // } - // ) - // this.user2.fullDeleteUser(this.user2.email, done) - // }) - // }) - }) - }) - - describe('unlink', function () { - describe('when provider not already linked', function () { - it('should succeed', function (done) { - return ThirdPartyIdentityManager.unlink( - this.user.id, - this.provider, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, res) => { - expect(err).to.be.null - expect(res.thirdPartyIdentifiers.length).to.equal(0) - return done() - } - ) - }) - }) - - describe('when provider is already linked', function () { - beforeEach(function (done) { - return ThirdPartyIdentityManager.link( - this.user.id, - this.provider, - this.externalUserId, - this.externalData, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - done - ) - }) - - it('should remove thirdPartyIdentifiers entry', function (done) { - return ThirdPartyIdentityManager.unlink( - this.user.id, - this.provider, - { initiatorId: this.user.id, ipAddress: '0:0:0:0' }, - (err, user) => { - expect(user.thirdPartyIdentifiers.length).to.equal(0) - return done() - } - ) - }) - }) - }) -}) diff --git a/services/web/test/acceptance/src/helpers/MongoHelper.js b/services/web/test/acceptance/src/helpers/MongoHelper.js index 9c2a0505cc..4c7fd6163f 100644 --- a/services/web/test/acceptance/src/helpers/MongoHelper.js +++ b/services/web/test/acceptance/src/helpers/MongoHelper.js @@ -7,8 +7,6 @@ module.exports = { before(function (done) { exec('bin/east migrate', (error, stdout, stderr) => { - console.log(stdout) - console.error(stderr) if (error) { throw error } diff --git a/services/web/test/unit/src/Project/ProjectDeleterTests.js b/services/web/test/unit/src/Project/ProjectDeleterTests.js index 65d4b00892..1ea1bb7680 100644 --- a/services/web/test/unit/src/Project/ProjectDeleterTests.js +++ b/services/web/test/unit/src/Project/ProjectDeleterTests.js @@ -132,9 +132,13 @@ describe('ProjectDeleter', function () { deleteProject: sinon.stub().resolves(), }, } + this.Features = { + hasFeature: sinon.stub().returns(true), + } this.ProjectDeleter = SandboxedModule.require(modulePath, { requires: { + '../../infrastructure/Features': this.Features, '../Editor/EditorRealTimeController': this.EditorRealTimeController, '../../models/Project': { Project: Project }, './ProjectHelper': this.ProjectHelper, @@ -486,6 +490,53 @@ describe('ProjectDeleter', function () { }) }) + describe('when history-v1 is not available', function () { + beforeEach(async function () { + this.Features.hasFeature.returns(false) + + this.ProjectMock.expects('findById') + .withArgs(this.deletedProjects[0].deleterData.deletedProjectId) + .chain('exec') + .resolves(null) + this.DeletedProjectMock.expects('updateOne') + .withArgs( + { + _id: this.deletedProjects[0]._id, + }, + { + $set: { + 'deleterData.deleterIpAddress': null, + project: null, + }, + } + ) + .chain('exec') + .resolves() + + this.DeletedProjectMock.expects('findOne') + .withArgs({ + 'deleterData.deletedProjectId': this.deletedProjects[0].project._id, + }) + .chain('exec') + .resolves(this.deletedProjects[0]) + + await this.ProjectDeleter.promises.expireDeletedProject( + this.deletedProjects[0].project._id + ) + }) + + it('should destroy the docs in docstore', function () { + expect( + this.DocstoreManager.promises.destroyProject + ).to.have.been.calledWith(this.deletedProjects[0].project._id) + }) + + it('should not call project history', function () { + expect(this.HistoryManager.promises.deleteProject).to.not.have.been + .called + }) + }) + describe('on an active project (from an incomplete delete)', function () { beforeEach(async function () { this.ProjectMock.expects('findById') diff --git a/services/web/test/unit/src/infrastructure/FeaturesTests.js b/services/web/test/unit/src/infrastructure/FeaturesTests.js index 7d2068e762..be64d5907c 100644 --- a/services/web/test/unit/src/infrastructure/FeaturesTests.js +++ b/services/web/test/unit/src/infrastructure/FeaturesTests.js @@ -6,7 +6,9 @@ describe('Features', function () { beforeEach(function () { this.Features = SandboxedModule.require(modulePath, { requires: { - '@overleaf/settings': (this.settings = {}), + '@overleaf/settings': (this.settings = { + moduleImportSequence: [], + }), }, }) }) @@ -45,10 +47,11 @@ describe('Features', function () { describe('hasFeature', function () { describe('without any settings', function () { it('should return true', function () { - expect(this.Features.hasFeature('registration')).to.be.true + expect(this.Features.hasFeature('registration-page')).to.be.true expect(this.Features.hasFeature('templates-server-pro')).to.be.true }) it('should return false', function () { + expect(this.Features.hasFeature('registration')).to.be.false expect(this.Features.hasFeature('affiliations')).to.be.false expect(this.Features.hasFeature('analytics')).to.be.false expect(this.Features.hasFeature('custom-togglers')).to.be.false