mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-26 11:42:12 +00:00
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
This commit is contained in:
parent
411a12cb2d
commit
fe4c48b7fb
36 changed files with 363 additions and 5176 deletions
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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']))
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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')}
|
||||
|
||||
|
|
196
services/web/test/acceptance/config/settings.test.defaults.js
Normal file
196
services/web/test/acceptance/config/settings.test.defaults.js
Normal file
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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/)
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 }
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
)
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue