diff --git a/src/commands/email/send.js b/src/commands/email/send.js index c6c24e6b4..2876e4d22 100644 --- a/src/commands/email/send.js +++ b/src/commands/email/send.js @@ -6,6 +6,12 @@ const { readFileOrStdIn, readFile } = require('../../services/file-io'); const sgMail = require('@sendgrid/mail'); const FileType = require('file-type'); +const DEFAULT_ENCODING = 'base64'; +const FLAGS = { + attachment: 'attachment', + noAttachment: 'no-attachment' +}; + class Send extends BaseCommand { async run() { await super.run(); @@ -37,18 +43,21 @@ class Send extends BaseCommand { html: '

' + this.emailText + '

' }; - const fileInfo = await readFileOrStdIn(this.flags.attachment); + if (!this.flags[FLAGS.noAttachment]) { + const fileInfo = await readFileOrStdIn(this.flags[FLAGS.attachment], DEFAULT_ENCODING); - if (fileInfo) { - sendInformation.attachments = await this.createAttachmentArray(fileInfo); - } else { - const attachmentVerdict = await this.askAttachment(); - const attachment = await this.promptAttachment(attachmentVerdict); + if (fileInfo) { + sendInformation.attachments = await this.createAttachmentArray(fileInfo); + } else { + const attachmentVerdict = await this.askAttachment(); + const attachment = await this.promptAttachment(attachmentVerdict); - if (attachment) { - sendInformation.attachments = await this.createAttachmentArray(readFile(attachment)); + if (attachment) { + sendInformation.attachments = await this.createAttachmentArray(readFile(attachment, DEFAULT_ENCODING)); + } } } + await this.sendEmail(sendInformation); } @@ -78,7 +87,7 @@ class Send extends BaseCommand { async createAttachmentArray(fileInfo) { // readFile and readFileOrStdIn return a base64 encoded string - const type = await FileType.fromBuffer(Buffer.from(fileInfo.content, 'base64')); + const type = await FileType.fromBuffer(Buffer.from(fileInfo.content, DEFAULT_ENCODING)); return [ { @@ -92,26 +101,26 @@ class Send extends BaseCommand { } validateEmail(email) { - let emailList = []; + let emailList; let validEmail = true; - const multipleEmail = email.includes(','); - if (multipleEmail === true) { - emailList = email.split(',').map(item => { - return item.trim(); - }); + + if (email.includes(',')) { + emailList = email.split(',').map(item => item.trim()); } else { - emailList[0] = email; + emailList = [email]; } emailList.forEach(emailAddress => { - if (emailUtilities.validateEmail(emailAddress) === false) { - this.logger.error(emailAddress + ' is not a valid email.'); + if (!emailUtilities.validateEmail(emailAddress)) { + this.logger.error(`"${emailAddress}" is not a valid email.`); validEmail = false; } }); - if (validEmail === false) { + + if (!validEmail) { throw new TwilioCliError('Email could not be sent, please re-run the command with valid email addresses.'); } + return emailList; } @@ -185,8 +194,14 @@ Send.flags = Object.assign( text: flags.string({ description: 'Text to send within the email body.' }), - attachment: flags.string({ - description: 'Path for the file that you want to attach.' + [FLAGS.attachment]: flags.string({ + description: 'Path for the file that you want to attach.', + exclusive: [FLAGS.noAttachment] + }), + [FLAGS.noAttachment]: flags.boolean({ + description: 'Do not include or prompt for an attachment.', + default: false, + exclusive: [FLAGS.attachment] }) }, BaseCommand.flags diff --git a/src/services/file-io.js b/src/services/file-io.js index a123604dc..ce7fca0a7 100644 --- a/src/services/file-io.js +++ b/src/services/file-io.js @@ -3,12 +3,12 @@ const { logger } = require('@twilio/cli-core').services.logging; const fs = require('fs'); const path = require('path'); -async function readFileOrStdIn(filePath) { +async function readFileOrStdIn(filePath, encoding) { if (filePath) { - return readFile(filePath); + return readFile(filePath, encoding); } - const pipedInput = await readStream(); + const pipedInput = await readStream(encoding); if (pipedInput) { return { filename: 'piped.txt', // placeholder filename for attachment @@ -17,11 +17,11 @@ async function readFileOrStdIn(filePath) { } } -function readFile(filePath) { +function readFile(filePath, encoding) { try { return { filename: path.basename(filePath), - content: fs.readFileSync(filePath, 'base64') + content: fs.readFileSync(filePath, encoding) }; } catch (error) { logger.debug(error); @@ -29,9 +29,9 @@ function readFile(filePath) { } } -async function readStream() { +async function readStream(encoding) { const input = await getStdin(); - return Buffer.from(input).toString('base64'); + return Buffer.from(input).toString(encoding); } function getStdin() { diff --git a/test/commands/email/send.test.js b/test/commands/email/send.test.js index fdd424cbc..a7bc7e1de 100644 --- a/test/commands/email/send.test.js +++ b/test/commands/email/send.test.js @@ -38,6 +38,11 @@ describe('commands', () => { path: filePath }); ctx.testCmd.inquirer.prompt = fakePrompt; + + process.env.SENDGRID_API_KEY = 'SG.1234567890'; + }) + .nock('https://api.sendgrid.com', api => { + api.post('/v3/mail/send').optionally().reply(200, {}); }); const noDefault = ({ @@ -53,8 +58,7 @@ describe('commands', () => { .stdout() .stderr() .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1134567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - var fakePrompt = sinon.stub(); + const fakePrompt = sinon.stub(); fakePrompt.resolves({ to: toEmail, from: fromEmail, @@ -62,10 +66,18 @@ describe('commands', () => { text: bodyText }); ctx.testCmd.inquirer.prompt = fakePrompt; + + process.env.SENDGRID_API_KEY = 'SG.1234567890'; + }) + .nock('https://api.sendgrid.com', api => { + api.post('/v3/mail/send').optionally().reply(200, {}); }); defaultSetup({ toEmail: 'jen@test.com , mike@test.com, tamu@test.com' }) - .do(ctx => ctx.testCmd.run()) + .do(ctx => { + delete process.env.SENDGRID_API_KEY; + return ctx.testCmd.run(); + }) .catch(/SENDGRID_API_KEY/) .it('run email:send with no environment variable for SendGrid key'); @@ -80,7 +92,7 @@ describe('commands', () => { .it( 'run email:send without defaults and with multiple recipients including an incorrect to email address', ctx => { - expect(ctx.stderr).to.contain('JonSnow is not a valid email'); + expect(ctx.stderr).to.contain('"JonSnow" is not a valid email'); } ); @@ -90,9 +102,6 @@ describe('commands', () => { subjectLine: 'Secret Message', bodyText: 'You know nothing Jon Snow.' }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) .do(ctx => ctx.testCmd.run()) .it('run email:send with filled out inquirer prompts', ctx => { expect(ctx.stderr).to.contain('You know nothing Jon Snow'); @@ -107,9 +116,6 @@ describe('commands', () => { flags: ['--subject', 'Open ASAP'], bodyText: 'You know nothing Jon Snow.' }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) .do(ctx => ctx.testCmd.run()) .it('run email:send use inquire and a flag to set information', ctx => { expect(ctx.stderr).to.contain('You know nothing Jon Snow'); @@ -127,17 +133,11 @@ describe('commands', () => { .do(ctx => ctx.testCmd.run()) .catch(/Email could not be sent/) .it('run email:send without defaults and an invalid from email address', ctx => { - expect(ctx.stderr).to.contain('Ygritte is not a valid email'); + expect(ctx.stderr).to.contain('"Ygritte" is not a valid email'); }); defaultSetup({ toEmail: 'jen@test.com' }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send with default subject line and sending email address', ctx => { expect(ctx.stderr).to.contain('Hello world'); expect(ctx.stderr).to.contain('default@test.com'); @@ -146,13 +146,7 @@ describe('commands', () => { }); defaultSetup({ toEmail: 'jen@test.com, mike@test.com, tamu@test.com' }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send with defaults and multiple recipients', ctx => { expect(ctx.stderr).to.contain('Hello world'); expect(ctx.stderr).to.contain('default@test.com'); @@ -174,13 +168,7 @@ describe('commands', () => { 'Short cuts make delays, but inns make longer ones.' ] }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send with all flags', ctx => { expect(ctx.stderr).to.contain('Short cuts make delays, but inns make longer ones'); expect(ctx.stderr).to.contain('Bilbo@test.com'); @@ -198,13 +186,7 @@ describe('commands', () => { 'Short cuts make delays, but inns make longer ones.' ] }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send with flags and default subject line', ctx => { expect(ctx.stderr).to.contain('Short cuts make delays, but inns make longer ones'); expect(ctx.stderr).to.contain('Bilbo@test.com'); @@ -226,13 +208,7 @@ describe('commands', () => { 'test/commands/email/test.txt' ] }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send using flags to set information using relative file path', ctx => { expect(ctx.stderr).to.contain('You know nothing Jon Snow'); expect(ctx.stderr).to.contain('Ygritte@wall.com'); @@ -255,21 +231,12 @@ describe('commands', () => { 'test/commands/email/invalid.txt' ] }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .catch(/Unable to read the file/) .it('run email:send using flags to set information using invalid file path'); defaultSetup({ toEmail: 'jen@test.com', attachmentVerdict: true }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it( 'run email:send with default subject line and sending email address and relative path for attachment', ctx => { @@ -281,6 +248,12 @@ describe('commands', () => { } ); + defaultSetup({ flags: ['--no-attachment'], toEmail: 'me@you.com', attachmentVerdict: true }) + .do(ctx => ctx.testCmd.run()) + .it('runs email:send without an attachment or prompt for one', ctx => { + expect(ctx.stderr).to.not.contain('test.txt'); + }); + defaultSetup({ flags: [ '--subject', @@ -293,14 +266,8 @@ describe('commands', () => { 'You know nothing Jon Snow.' ] }) - .nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }) .stdin('this is some piped data', 100) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .it('run email:send using stdin as the attachment source', ctx => { expect(ctx.stderr).to.contain('You know nothing Jon Snow'); expect(ctx.stderr).to.contain('Ygritte@wall.com'); @@ -313,10 +280,7 @@ describe('commands', () => { flags: ['--from', 'Ygritte@wall.com', '--text', 'You know nothing Jon Snow.'] }) .stdin('this is some piped data', 100) - .do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }) + .do(ctx => ctx.testCmd.run()) .catch(/No terminal.*Please provide/) .it('run email:send using stdin as the attachment source but missing a To'); @@ -333,15 +297,12 @@ describe('commands', () => { '--attachment', 'test/commands/email/test.txt' ] - }).nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }).do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }).it('run email:send with an attachment and set the correct content-type for plaintext files', ctx => { - expect(ctx.stderr).to.contain('test.txt'); - expect(ctx.stderr).to.contain('text/plain'); - }); + }) + .do(ctx => ctx.testCmd.run()) + .it('run email:send with an attachment and set the correct content-type for plaintext files', ctx => { + expect(ctx.stderr).to.contain('test.txt'); + expect(ctx.stderr).to.contain('text/plain'); + }); defaultSetup({ flags: [ @@ -356,15 +317,12 @@ describe('commands', () => { '--attachment', 'test/commands/email/test.png' ] - }).nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }).do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }).it('run email:send with an attachment and set the correct content-type for non-text files', ctx => { - expect(ctx.stderr).to.contain('test.png'); - expect(ctx.stderr).to.contain('image/png'); - }); + }) + .do(ctx => ctx.testCmd.run()) + .it('run email:send with an attachment and set the correct content-type for non-text files', ctx => { + expect(ctx.stderr).to.contain('test.png'); + expect(ctx.stderr).to.contain('image/png'); + }); defaultSetup({ flags: [ @@ -379,15 +337,12 @@ describe('commands', () => { '--attachment', 'test/commands/email/test.bin' ] - }).nock('https://api.sendgrid.com', api => { - api.post('/v3/mail/send').reply(200, {}); - }).do(ctx => { - process.env.SENDGRID_API_KEY = 'SG.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef_4'; - return ctx.testCmd.run(); - }).it('run email:send with an attachment and fall back to text/plain for unknown file types', ctx => { - expect(ctx.stderr).to.contain('test.bin'); - expect(ctx.stderr).to.contain('text/plain'); - }); + }) + .do(ctx => ctx.testCmd.run()) + .it('run email:send with an attachment and fall back to text/plain for unknown file types', ctx => { + expect(ctx.stderr).to.contain('test.bin'); + expect(ctx.stderr).to.contain('text/plain'); + }); }); }); });