Test monthly email script, add limit

* Move the script logic into functions so that running and
  initialization can be skipped in tests.
* Add a test to send an email to the test subscriber
* Add MONTHLY_CRON_LIMIT sets the maximum number of emails to send.
* Refactor the database functions to allow a limit and a count of total
  subscribers.
This commit is contained in:
John Whitlock 2022-09-09 14:57:56 -05:00
Родитель d0b370eb5f
Коммит c58bbdb041
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 082C735D154FB750
4 изменённых файлов: 115 добавлений и 32 удалений

Просмотреть файл

@ -57,7 +57,8 @@ const optionalEnvVars = [
'EDUCATION_VIDEO_URL_RELAY',
'EDUCATION_VIDEO_URL_VPN',
'ADMINS',
'MONTHLY_CRON_ENABLED'
'MONTHLY_CRON_ENABLED',
'MONTHLY_CRON_LIMIT'
]
const AppConstants = { }

Просмотреть файл

@ -439,15 +439,27 @@ const DB = {
await knex('subscribers').update('monthly_email_optout', true).where('primary_verification_token', token)
},
async getSubscribersWithUnresolvedBreaches () {
const res = await knex('subscribers')
.select('primary_email', 'primary_verification_token', 'breach_stats', 'signup_language')
getSubscribersWithUnresolvedBreachesQuery () {
return knex('subscribers')
.whereRaw('monthly_email_optout IS NOT TRUE')
.whereRaw("greatest(created_at, monthly_email_at) < (now() - interval '30 days')")
// .whereJsonPath('breach_stats', '$.numBreaches.numUnresolved', '>', 0) // Requires psql 11
.whereRaw("(breach_stats #>> '{numBreaches, numUnresolved}')::int > 0")
},
return res
async getSubscribersWithUnresolvedBreaches (limit = 0) {
let query = this.getSubscribersWithUnresolvedBreachesQuery()
.select('primary_email', 'primary_verification_token', 'breach_stats', 'signup_language')
if (limit && limit > 0) {
query = query.limit(limit).orderBy('created_at')
}
return await query
},
async getSubscribersWithUnresolvedBreachesCount () {
const query = this.getSubscribersWithUnresolvedBreachesQuery()
const count = parseInt((await query.count({ count: '*' }))[0].count)
return count
},
async createConnection () {
@ -457,8 +469,10 @@ const DB = {
},
async destroyConnection () {
await knex.destroy()
knex = null
if (knex !== null) {
await knex.destroy()
knex = null
}
}
}

Просмотреть файл

@ -2,18 +2,28 @@ const DB = require('../db/DB')
const EmailUtils = require('../email-utils')
const { LocaleUtils } = require('../locale-utils')
const AppConstants = require('../app-constants')
const { argv } = require('node:process')
const { env, argv } = require('node:process')
async function sendUnresolvedBreachEmails (subscribers = null) {
/* Send a monthly email to each subscriber with unresolved breaches
*
* Usage:
* node scripts/send-email-to-unresolved-breach-subscribers.js
*
* For testing, pass a comma-separated arg with no spaces, for example:
* node scripts/send-email-to-unresolved-breach-subscribers.js test1@test.com,test2@test.com,...
*/
async function sendUnresolvedBreachEmails (subscribers = null, limit = 0) {
let count = 0
LocaleUtils.init()
await EmailUtils.init()
await DB.createConnection()
if (!subscribers) subscribers = await DB.getSubscribersWithUnresolvedBreaches() // if test subscribers are not available, pull from db
console.log('- Recipient total:', subscribers.length)
let total = 0
if (subscribers) {
total = subscribers.length
} else {
// if test subscribers are not available, pull from db
total = await DB.getSubscribersWithUnresolvedBreachesCount()
subscribers = await DB.getSubscribersWithUnresolvedBreaches(limit)
}
console.log(`- Attempting to email ${subscribers.length} of ${total} subscribers...`)
for (const subscriber of subscribers) {
try {
@ -38,18 +48,41 @@ async function sendUnresolvedBreachEmails (subscribers = null) {
}
}
await DB.destroyConnection()
console.log(`- Successfully emailed ${count} of ${subscribers.length} subscribers`)
console.log(`- Successfully emailed ${count} of ${subscribers.length} subscribers (${total} total)`)
return {
emailed: count,
attempted: subscribers.length,
total
}
}
if (argv[2]) {
// For testing, pass a comma-separated arg with no spaces, for example:
// node scripts/send-email-to-unresolved-breach-subscribers.js test1@test.com,test2@test.com,...
const subscribers = []
const emails = argv[2].split(',')
async function init () {
LocaleUtils.init()
await EmailUtils.init()
await DB.createConnection()
}
console.log('Testing job for monthly unresolved breach emails...')
console.log('- sending test emails to', emails)
async function runScript (argv = []) {
let subscribers = null
const limit = AppConstants.MONTHLY_CRON_LIMIT
if (argv[2]) {
console.log('Testing job for monthly unresolved breach emails...')
const res = await prepareTestSubscribers(argv[2])
subscribers = res.subscribers
console.log('- Sending test emails to', res.emails)
} else if (AppConstants.MONTHLY_CRON_ENABLED === 'true') {
console.log('Running job for monthly unresolved breach emails...')
} else {
console.log('Job for monthly unresolved breach emails was not run. MONTHLY_CRON_ENABLED expects the string "true".')
return
}
return await sendUnresolvedBreachEmails(subscribers, limit)
}
async function prepareTestSubscribers (emailCsv = '') {
// Create subscriber records from a comma-separated list of emails
const subscribers = []
const emails = emailCsv.split(',')
emails.forEach(email => {
subscribers.push({
@ -72,10 +105,28 @@ if (argv[2]) {
}
})
})
sendUnresolvedBreachEmails(subscribers)
} else if (AppConstants.MONTHLY_CRON_ENABLED === 'true') {
console.log('Running job for monthly unresolved breach emails...')
sendUnresolvedBreachEmails()
} else {
console.log('Job for monthly unresolved breach emails was not run. MONTHLY_CRON_ENABLED expects the string "true".')
return {
subscribers,
emails
}
}
async function teardown () {
await DB.destroyConnection()
}
async function main () {
if (env.NODE_ENV !== 'tests') {
await init()
try {
await runScript(argv)
} finally {
await teardown()
}
}
}
main()
module.exports = {
sendUnresolvedBreachEmails
}

Просмотреть файл

@ -0,0 +1,17 @@
const DB = require('../db/DB')
const { TEST_SUBSCRIBERS, TEST_EMAIL_ADDRESSES } = require('../db/seeds/test_subscribers')
const { sendUnresolvedBreachEmails } = require('../scripts/send-email-to-unresolved-breach-subscribers')
const EmailUtils = require('../email-utils')
require('./resetDB')
jest.mock('../email-utils')
test('sendUnresolvedBreachEmails to test subscriber', async () => {
const data = await sendUnresolvedBreachEmails()
expect(data).toEqual({"attempted": 1, "emailed": 1, "total": 1})
expect(EmailUtils.sendEmail).toHaveBeenCalled()
const subscriber = await DB.getSubscriberById(12346)
expect(subscriber.monthly_email_at).not.toBeNull()
expect(Date.now() - subscriber.monthly_email_at).toBeLessThan(100)
})