diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index 9c65de2b14..4f46d1cdc9 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -1,10 +1,105 @@ import { isExcludedBySharding, startWith } from './helpers/config' -import { activateUser, ensureUserExists, login } from './helpers/login' +import { + activateUser, + createMongoUser, + ensureUserExists, + login, +} from './helpers/login' import { v4 as uuid } from 'uuid' import { createProject } from './helpers/project' import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' +import { openEmail } from './helpers/email' describe('admin panel', function () { + function registrationTests() { + it('via GUI and opening URL manually', () => { + const user = `${uuid()}@example.com` + cy.get('input[name="email"]').type(user + '{enter}') + + cy.get('td') + .contains(/\/user\/activate/) + .then($td => { + const url = $td.text().trim() + activateUser(url) + }) + }) + + it('via GUI and email', () => { + const user = `${uuid()}@example.com` + cy.get('input[name="email"]').type(user + '{enter}') + + let url: string + cy.get('td') + .contains(/\/user\/activate/) + .then($td => { + url = $td.text().trim() + }) + + cy.then(() => { + openEmail( + 'Activate your Overleaf Community Edition Account', + (frame, { url }) => { + frame.contains('Set password').then(el => { + expect(el.attr('href')!).to.equal(url) + }) + }, + { url } + ) + // Run activateUser in the main origin instead of inside openEmail. See docs on openEmail. + activateUser(url) + }) + }) + it('via script and opening URL manually', () => { + const user = `${uuid()}@example.com` + let url: string + cy.then(async () => { + ;({ url } = await createMongoUser({ email: user })) + }) + cy.then(() => { + activateUser(url) + }) + }) + it('via script and email', () => { + const user = `${uuid()}@example.com` + let url: string + cy.then(async () => { + ;({ url } = await createMongoUser({ email: user })) + }) + cy.then(() => { + openEmail( + 'Activate your Overleaf Community Edition Account', + (frame, { url }) => { + frame.contains('Set password').then(el => { + expect(el.attr('href')!).to.equal(url) + }) + }, + { url } + ) + // Run activateUser in the main origin instead of inside openEmail. See docs on openEmail. + activateUser(url) + }) + }) + } + + describe('in CE', () => { + if (isExcludedBySharding('CE_DEFAULT')) return + startWith({ pro: false, version: 'latest' }) + const admin = 'admin@example.com' + const user = `user+${uuid()}@example.com` + ensureUserExists({ email: admin, isAdmin: true }) + ensureUserExists({ email: user }) + + describe('create users', () => { + beforeEach(() => { + login(admin) + cy.visit('/project') + cy.get('nav').findByText('Admin').click() + cy.get('nav').findByText('Manage Users').click() + }) + registrationTests() + }) + }) + describe('in server pro', () => { const admin = 'admin@example.com' const user1 = 'user@example.com' @@ -83,18 +178,11 @@ describe('admin panel', function () { cy.get('nav').findByText('Manage Users').click() }) - it('create and login user', () => { - const user = `${uuid()}@example.com` - - cy.get('a').contains('New User').click() - cy.get('input[name="email"]').type(user + '{enter}') - - cy.get('td') - .contains(/\/user\/activate/) - .then($td => { - const url = $td.text().trim() - activateUser(url) - }) + describe('create users', () => { + beforeEach(() => { + cy.get('a').contains('New User').click() + }) + registrationTests() }) it('user list RegExp search', () => { diff --git a/server-ce/test/helpers/config.ts b/server-ce/test/helpers/config.ts index 1cd86db564..453ddca425 100644 --- a/server-ce/test/helpers/config.ts +++ b/server-ce/test/helpers/config.ts @@ -1,5 +1,5 @@ import { reconfigure } from './hostAdminClient' -import { resetCreatedUsersCache } from './login' +import { resetActivateUserRateLimit, resetCreatedUsersCache } from './login' export const STARTUP_TIMEOUT = parseInt(Cypress.env('STARTUP_TIMEOUT'), 10) || 120_000 @@ -39,6 +39,7 @@ export function startWith({ }) if (resetData) { resetCreatedUsersCache() + resetActivateUserRateLimit() // no return here, always reconfigure when resetting data } else if (lastConfig === cfg) { return diff --git a/server-ce/test/helpers/email.ts b/server-ce/test/helpers/email.ts index b5f059d6df..5ffc9bc7c1 100644 --- a/server-ce/test/helpers/email.ts +++ b/server-ce/test/helpers/email.ts @@ -2,12 +2,12 @@ * Helper function for opening an email in Roundcube based mailtrap. * We need to cross an origin boundary, which complicates the use of variables. * Any variables need to be explicitly defined and the "runner" may only reference these and none from its scope. - * It is not possible to use Cypress helper functions, e.g. from the testing library inside the "runner". + * It is not possible to use Cypress helper functions, e.g. from the testing library or other functions like "activateUser", inside the "runner". * REF: https://github.com/testing-library/cypress-testing-library/issues/221 */ export function openEmail( subject: string | RegExp, - runner: (frame: Cypress.Chainable>, args?: T) => void, + runner: (frame: Cypress.Chainable>, args: T) => void, args?: T ) { const runnerS = runner.toString() diff --git a/server-ce/test/helpers/login.ts b/server-ce/test/helpers/login.ts index 8396a92425..c1ecbcfd05 100644 --- a/server-ce/test/helpers/login.ts +++ b/server-ce/test/helpers/login.ts @@ -21,7 +21,7 @@ export async function createMongoUser({ script: 'modules/server-ce-scripts/scripts/create-user.js', args: [`--email=${email}`, `--admin=${isAdmin}`], }) - const [url] = stdout.match(/\/user\/activate\?token=\S+/)! + const [url] = stdout.match(/http:\/\/.+\/user\/activate\?token=\S+/)! const userId = new URL(url, location.origin).searchParams.get('user_id')! const signupDate = parseInt(userId.slice(0, 8), 16) if (signupDate < t0) { @@ -70,7 +70,27 @@ export function login(username: string, password = DEFAULT_PASSWORD) { return startOrResumeSession } +let activateRateLimitState = { count: 0, reset: 0 } +export function resetActivateUserRateLimit() { + activateRateLimitState = { count: 0, reset: 0 } +} + +function handleActivateUserRateLimit() { + cy.then(() => { + activateRateLimitState.count++ + if (activateRateLimitState.reset < Date.now()) { + activateRateLimitState.reset = Date.now() + 65_000 + activateRateLimitState.count = 1 + } else if (activateRateLimitState.count >= 6) { + cy.wait(activateRateLimitState.reset - Date.now()) + activateRateLimitState.count = 1 + } + }) +} + export function activateUser(url: string, password = DEFAULT_PASSWORD) { + handleActivateUserRateLimit() + cy.session(url, () => { cy.visit(url) cy.url().then(url => { diff --git a/server-ce/test/host-admin.js b/server-ce/test/host-admin.js index 1a56b07df6..3da871e014 100644 --- a/server-ce/test/host-admin.js +++ b/server-ce/test/host-admin.js @@ -159,6 +159,7 @@ const allowedVars = Joi.object( 'OVERLEAF_LDAP_LAST_NAME_ATT', 'OVERLEAF_LDAP_UPDATE_USER_DETAILS_ON_LOGIN', // Old branding, used for upgrade tests + 'SHARELATEX_SITE_URL', 'SHARELATEX_MONGO_URL', 'SHARELATEX_REDIS_HOST', ].map(name => [name, Joi.string()]) diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index c0e5fa63a4..86a3ea0cad 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -145,7 +145,8 @@ describe('Upgrading', function () { const optionsFourDotTwo = { version: '4.2', vars: { - // Add database vars with old branding + // Add core vars with old branding + SHARELATEX_SITE_URL: 'http://sharelatex', SHARELATEX_MONGO_URL: 'mongodb://mongo/sharelatex', SHARELATEX_REDIS_HOST: 'redis', },