Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions env/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const base = removeNullValues({
TWILIO_TOKEN,
TWILIO_FAX_NUMBER,
DIVERT_FAX_NUMBER,
DIVERT_EMAIL: 'fake_voter@votebymail.io',
SENDINBLUE_API_KEY, // We are using the v3 API
SENDINBLUE_LIST_ID: 4, // This is just a fake testing list -- no emails will be sent
USER_MAX_ORGS: 50,
Expand Down Expand Up @@ -131,6 +132,7 @@ const production = removeNullValues({
GOOGLE_CLIENT_CALLBACK: 'https://app.votebymail.io/auth/google/callback',
GOOGLE_STORAGE_BUCKET: 'vbm-prod-281822.appspot.com',
EMAIL_FAX_OFFICIALS: 1,
DIVERT_EMAIL: undefined,
DIVERT_FAX_NUMBER: undefined,
RECORDS_ADDR: 'records@votebymail.io',
ALLOY_MOCK: undefined,
Expand Down
3 changes: 2 additions & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
"retry-axios": "^2.2.1",
"sib-api-v3-typescript": "^1.2.1",
"strip-indent": "^3.0.0",
"twilio": "^3.43.0"
"twilio": "^3.43.0",
"yargs": "^16.0.3"
},
"jest": {
"globals": {
Expand Down
27 changes: 19 additions & 8 deletions packages/server/src/script/loadRegistrationTesting.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// To test this script for faxes run it using --faxes on the CLI, or by
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to send actual emails or faxes during the test. Is there a way to prevent this or to mock what gets sent?

// manually passing `forceFaxes = true` on `main`

import { sampleStateInfo } from '../service/letter'
import { contactRecords } from '../service/contact'
import { makeClient, httpConnector } from '@tianhuil/simple-trpc/dist/client'
import { IVbmRpc, processEnvOrThrow, wait } from '../common'
import { promises as fs } from 'fs'
import fetch from 'node-fetch'
import { RpcRet } from '@tianhuil/simple-trpc/dist/type'
import yargs from 'yargs'

const serverUrl = processEnvOrThrow('REACT_APP_SERVER')
const timeout = parseInt(processEnvOrThrow('REACT_APP_TIMEOUT'))
Expand All @@ -13,7 +17,12 @@ export const client = makeClient<IVbmRpc>(
httpConnector(serverUrl, { timeout, fetch })
)

const fakeEmail = 'fake_voter@votebymail.io'
const fakeEmail = processEnvOrThrow('DIVERT_EMAIL')

/**
* Returns true if this script is being run with the flag `--faxes`
*/
const hasFaxesFlag = yargs.argv.faxes ?? false

const delay = async <T>(fn: () => Promise<T>, ms: number): Promise<T> => {
await wait(ms)
Expand All @@ -34,8 +43,8 @@ interface QueryResponse {
* @param qps Amount of registrations to be sent per seconds
* @param duration Duration of this cycle in seconds
*/
const loadRegistrationTesting = async (qps: number, duration: number): Promise<QueryResponse[]> => {
const stateInfo = await sampleStateInfo('Florida')
const loadRegistrationTesting = async (qps: number, duration: number, forceFaxes?: boolean): Promise<QueryResponse[]> => {
const stateInfo = await sampleStateInfo((forceFaxes || hasFaxesFlag) ? 'Oklahoma' : 'Florida')
stateInfo.email = fakeEmail
// Creates an array that allow us to serialize N amount of queries per second through the given duration
const startTimes = Array(duration * qps).fill(0).map((_, index) => index * (1000 / qps))
Expand All @@ -60,9 +69,9 @@ const loadRegistrationTesting = async (qps: number, duration: number): Promise<Q
} catch(error) {
return {
error: `${error}`, // Always converts errors to strings
duration,
ms,
qps,
duration,
}
}
},
Expand All @@ -74,10 +83,11 @@ const loadRegistrationTesting = async (qps: number, duration: number): Promise<Q

export interface LoadTestStoredInfo {
startTime: string
method: 'fax' | 'email'
results: Array<QueryResponse>
}

const main = async () => {
const main = async (forceFaxes?: boolean) => {
// Prevents contact message from showing in the middle of one of the above
// functions.
await contactRecords
Expand All @@ -86,16 +96,17 @@ const main = async () => {
const timeStamp = Math.floor(startTime.getTime() / 1000)

const results = [
...await loadRegistrationTesting(1, 20), // One query per second
...await loadRegistrationTesting(3, 20), // Three queries per second
...await loadRegistrationTesting(10, 20), // Ten queries per second
...await loadRegistrationTesting(1, 20, forceFaxes), // One query per second
...await loadRegistrationTesting(3, 20, forceFaxes), // Three queries per second
...await loadRegistrationTesting(10, 20, forceFaxes), // Ten queries per second
]

console.log('Cycles completed, saving logs')

const file = `${__dirname}/data/loadRegistrationTesting-${timeStamp}.json`
const storedInfo: LoadTestStoredInfo = {
startTime: startTime.toISOString(),
method: (forceFaxes || hasFaxesFlag) ? 'fax' : 'email',
results,
}
// Formats the content of the file (it's very likely that the file is going to be read by humans)
Expand Down
8 changes: 8 additions & 0 deletions packages/server/src/script/verifyLoadTesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ const main = async (customFile?: string) => {
return null
}).filter(str => !!str) as string[] // Removes empty/null elements

if (storedInfo.method === 'fax') {
console.log(
'The last test was executed targetting faxes. ' +
'Unfortunately our fax API doesn\'t support detailed information for outbound faxes status.'
)
console.log('We\'ll still be able to track status of outbound email (for voters) in this script...')
}

await fetchIdStatuses(
storedInfo.results.length,
ids,
Expand Down
51 changes: 31 additions & 20 deletions packages/server/src/service/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface Options {
noUpload?: boolean
}

const divertEmail = process.env.DIVERT_EMAIL

export const sendAndStoreSignup = async (
info: StateInfo,
method: ContactMethod,
Expand All @@ -26,21 +28,28 @@ export const sendAndStoreSignup = async (
const letter = new Letter(info, method, id, resendReasonAndOriginalDate)
const pdfBuffer = await toPdfBuffer(letter.render('html'), await letter.form)
const isResend = resendReasonAndOriginalDate !== undefined
// When performing signups for DIVERT_EMAIL we don't send emails or faxes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong -- we use DIVERT_FAX_NUMBER and DIVERT_EMAIL separately. Let's stick to using that logic.

I don't think we should be editing signup.ts very much for this.

const sendEmailAndFax = info.email !== divertEmail
if (isResend && info?.id !== id) {
throw new Error('When resending emails, StateInfo should be RichStateInfo with id')
}

// Send email (perhaps only to voter)
const mgResponse = await sendSignupEmail(
letter,
info.email,
method.emails,
{ pdfBuffer, id },
)
if (!mgResponse || mgResponse.message !== 'Queued. Thank you.') {
console.error('Error sending email for ' + info.id + '.')
console.error(mgResponse)
return
// Send email (perhaps only to voter), and when the target email is not DIVERT_EMAIL
const mgResponse = sendEmailAndFax
? await sendSignupEmail(
letter,
info.email,
method.emails,
{ pdfBuffer, id },
)
: null

if (sendEmailAndFax) {
if (!mgResponse || mgResponse.message !== 'Queued. Thank you.') {
console.error('Error sending email for ' + info.id + '.')
console.error(mgResponse)
return
}
}

// Upload PDF
Expand All @@ -61,16 +70,18 @@ export const sendAndStoreSignup = async (

// Send faxes
let twilioResponses: TwilioResponse[] = []
if (method.faxes.length > 0) {
const uri = await file.getSignedUrl(24 * 60 * 60 * 1000)
const resposnes = await sendFaxes(uri, method.faxes)
twilioResponses = resposnes.map(({url, sid, status}) => ({url, sid, status}))
if (sendEmailAndFax) {
if (method.faxes.length > 0) {
const uri = await file.getSignedUrl(24 * 60 * 60 * 1000)
const resposnes = await sendFaxes(uri, method.faxes)
twilioResponses = resposnes.map(({url, sid, status}) => ({url, sid, status}))

for (const twilioResponse of twilioResponses) {
if (twilioResponse.status !== 'queued') {
console.error(`Error sending faxes for ${info.id ?? id}.`)
console.error(twilioResponse)
return
for (const twilioResponse of twilioResponses) {
if (twilioResponse.status !== 'queued') {
console.error(`Error sending faxes for ${info.id ?? id}.`)
console.error(twilioResponse)
return
}
}
}
}
Expand Down
48 changes: 47 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5679,6 +5679,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"

cliui@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3"
integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"

clone-buffer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
Expand Down Expand Up @@ -7587,6 +7596,11 @@ escalade@^3.0.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed"
integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==

escalade@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==

escape-goat@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
Expand Down Expand Up @@ -8874,7 +8888,7 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==

get-caller-file@^2.0.1:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
Expand Down Expand Up @@ -19404,6 +19418,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
Expand Down Expand Up @@ -19567,6 +19590,11 @@ y18n@^3.2.1:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==

y18n@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571"
integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg==

yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
Expand Down Expand Up @@ -19634,6 +19662,11 @@ yargs-parser@^18.1.1, yargs-parser@^18.1.3:
camelcase "^5.0.0"
decamelize "^1.2.0"

yargs-parser@^20.0.0:
version "20.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.0.0.tgz#c65a1daaa977ad63cebdd52159147b789a4e19a9"
integrity sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==

yargs@12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
Expand Down Expand Up @@ -19702,6 +19735,19 @@ yargs@^15.3.1:
y18n "^4.0.0"
yargs-parser "^18.1.1"

yargs@^16.0.3:
version "16.0.3"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c"
integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==
dependencies:
cliui "^7.0.0"
escalade "^3.0.2"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.1"
yargs-parser "^20.0.0"

yargs@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6"
Expand Down