| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- import { promisify } from 'node:util'
- import { exec } from 'node:child_process'
- import { expect } from 'chai'
- import { filterOutput } from './helpers/settings.mjs'
- import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js'
- import fs from 'node:fs/promises'
- const CSV_FILENAME = '/tmp/remove_unconfirmed_emails.csv'
- async function runScript(mode, commit) {
- const result = await promisify(exec)(
- [
- 'node',
- 'scripts/remove_unconfirmed_emails.mjs',
- mode === 'generate' ? '--generate' : '--consume',
- commit && '--commit',
- ]
- .filter(Boolean)
- .join(' ')
- )
- return {
- ...result,
- stdout: result.stdout.split('\n').filter(filterOutput),
- }
- }
- function createUser(signUpDate, emails, userIdx) {
- const email = `primary${userIdx ?? ''}@overleaf.com`
- return {
- _id: new ObjectId(),
- email,
- emails,
- signUpDate,
- }
- }
- describe('scripts/remove_unconfirmed_emails', function () {
- let user
- afterEach(async function () {
- try {
- await fs.unlink(CSV_FILENAME)
- } catch (err) {
- // Ignore errors if file doesn't exist
- }
- })
- describe('when removing unconfirmed secondary emails', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com', confirmedAt: new Date() },
- { email: 'unconfirmed1@overleaf.com' },
- { email: 'unconfirmed-special-,\'"@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should remove all unconfirmed secondary emails', async function () {
- await runScript('generate')
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 2')
- expect(r.stdout).to.include('Total users processed: 1')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(1)
- expect(updatedUser.emails[0].email).to.equal(user.email)
- })
- it('should not modify anything in dry run mode', async function () {
- await runScript('generate')
- const r = await runScript('consume', false)
- expect(r.stdout).to.include('Total emails in the CSV: 2')
- expect(r.stdout).to.include('Total users processed: 1')
- expect(r.stdout).to.include(
- 'Note: this was a dry-run. No changes were made.'
- )
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(3)
- })
- })
- describe('when handling confirmed secondary emails', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com', confirmedAt: new Date() },
- { email: 'confirmed@overleaf.com', confirmedAt: new Date() },
- ])
- await db.users.insertOne(user)
- })
- it('should preserve confirmed secondary emails', async function () {
- await runScript('generate')
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 0')
- expect(r.stdout).to.include('Total users processed: 0')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(2)
- expect(updatedUser.emails[1].confirmedAt).to.exist
- })
- })
- describe('when handling unconfirmed primary emails', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should not remove unconfirmed primary emails', async function () {
- await runScript('generate')
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 0')
- expect(r.stdout).to.include('Total users processed: 0')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(1)
- expect(updatedUser.emails[0].email).to.equal('primary@overleaf.com')
- })
- })
- describe('when users confirmed their email in between', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com' },
- { email: 'secondary@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should not remove emails from users who confirmed their email in between', async function () {
- await runScript('generate')
- await db.users.updateOne(
- { _id: user._id },
- { $set: { 'emails.1.confirmedAt': new Date() } }
- )
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 1')
- expect(r.stdout).to.include('Skipped emails: 1')
- expect(r.stdout).to.include(' - Email now confirmed: 1')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(2)
- })
- })
- describe('when users changed their primary email in between', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com' },
- { email: 'secondary@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should not remove emails from users who changed their primary email in between', async function () {
- await runScript('generate')
- await db.users.updateOne(
- { _id: user._id },
- { $set: { email: 'secondary@overleaf.com' } }
- )
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 1')
- expect(r.stdout).to.include('Skipped emails: 1')
- expect(r.stdout).to.include(' - Email now primary: 1')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(2)
- })
- })
- describe('when users account was deleted in between', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com' },
- { email: 'secondary@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should skip emails from users whose account was deleted', async function () {
- await runScript('generate')
- // Delete the user
- await db.users.deleteOne({ _id: user._id })
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 1')
- expect(r.stdout).to.include('Skipped emails: 1')
- expect(r.stdout).to.include(' - User not found: 1')
- })
- })
- describe('when users email was deleted in between', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com' },
- { email: 'secondary@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should skip emails that were already removed', async function () {
- await runScript('generate')
- // Remove the secondary email
- await db.users.updateOne(
- { _id: user._id },
- { $pull: { emails: { email: 'secondary@overleaf.com' } } }
- )
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 1')
- expect(r.stdout).to.include('Skipped emails: 1')
- expect(r.stdout).to.include(' - Email now removed: 1')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(1)
- expect(updatedUser.emails[0].email).to.equal('primary@overleaf.com')
- })
- })
- describe('when handling confirmation field edge cases', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com', confirmedAt: new Date() },
- { email: 'secondary1@overleaf.com', confirmedAt: null },
- { email: 'secondary2@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should remove emails with both missing and null confirmedAt', async function () {
- await runScript('generate')
- const r = await runScript('consume', true)
- expect(r.stdout).to.include('Total emails in the CSV: 2')
- expect(r.stdout).to.include('Total users processed: 1')
- const updatedUser = await db.users.findOne({ _id: user._id })
- expect(updatedUser.emails).to.have.length(1)
- expect(updatedUser.emails[0].email).to.equal(user.email)
- })
- })
- describe('CSV file generation', function () {
- beforeEach(async function () {
- user = createUser(new Date('2000-01-01'), [
- { email: 'primary@overleaf.com', confirmedAt: new Date() },
- { email: 'unconfirmed1@overleaf.com' },
- { email: 'confirmed1@overleaf.com', confirmedAt: new Date() },
- { email: 'unconfirmed2@overleaf.com' },
- { email: '!,@overleaf.com' },
- { email: "!'@overleaf.com" },
- { email: '!,\'"@overleaf.com' },
- ])
- await db.users.insertOne(user)
- })
- it('should generate a valid CSV file', async function () {
- const r = await runScript('generate')
- expect(r.stdout).to.include(
- 'Generated CSV file: /tmp/remove_unconfirmed_emails.csv'
- )
- expect(r.stdout).to.include('Total emails in the CSV: 5')
- const csvContent = await fs.readFile(CSV_FILENAME, 'utf8')
- expect(csvContent).to.equal(`User ID,Email,Sign Up Date
- ${user._id},unconfirmed1@overleaf.com,2000-01-01T00:00:00.000Z
- ${user._id},unconfirmed2@overleaf.com,2000-01-01T00:00:00.000Z
- ${user._id},"!,@overleaf.com",2000-01-01T00:00:00.000Z
- ${user._id},!'@overleaf.com,2000-01-01T00:00:00.000Z
- ${user._id},"!,'""@overleaf.com",2000-01-01T00:00:00.000Z
- `)
- })
- })
- })
|