/* eslint no-unused-expressions:0 */ /* globals afterEach, beforeEach, describe, it */ 'use strict'; var chai = require('chai'); var sinon = require('sinon'); var Buildmail = require('../lib/buildmail'); var http = require('http'); var stream = require('stream'); var Transform = stream.Transform; var PassThrough = stream.PassThrough; var expect = chai.expect; chai.config.includeStack = true; describe('Buildmail', function () { it('should create Buildmail object', function () { expect(new Buildmail()).to.exist; }); describe('#createChild', function () { it('should create child', function () { var mb = new Buildmail('multipart/mixed'); var child = mb.createChild('multipart/mixed'); expect(child.parentNode).to.equal(mb); expect(child.rootNode).to.equal(mb); var subchild1 = child.createChild('text/html'); expect(subchild1.parentNode).to.equal(child); expect(subchild1.rootNode).to.equal(mb); var subchild2 = child.createChild('text/html'); expect(subchild2.parentNode).to.equal(child); expect(subchild2.rootNode).to.equal(mb); }); }); describe('#appendChild', function () { it('should append child node', function () { var mb = new Buildmail('multipart/mixed'); var child = new Buildmail('text/plain'); mb.appendChild(child); expect(child.parentNode).to.equal(mb); expect(child.rootNode).to.equal(mb); expect(mb.childNodes.length).to.equal(1); expect(mb.childNodes[0]).to.equal(child); }); }); describe('#replace', function () { it('should replace node', function () { var mb = new Buildmail(), child = mb.createChild('text/plain'), replacement = new Buildmail('image/png'); child.replace(replacement); expect(mb.childNodes.length).to.equal(1); expect(mb.childNodes[0]).to.equal(replacement); }); }); describe('#remove', function () { it('should remove node', function () { var mb = new Buildmail(), child = mb.createChild('text/plain'); child.remove(); expect(mb.childNodes.length).to.equal(0); expect(child.parenNode).to.not.exist; }); }); describe('#setHeader', function () { it('should set header', function () { var mb = new Buildmail(); mb.setHeader('key', 'value'); mb.setHeader('key', 'value1'); expect(mb.getHeader('Key')).to.equal('value1'); mb.setHeader([{ key: 'key', value: 'value2' }, { key: 'key2', value: 'value3' }]); expect(mb._headers).to.deep.equal([{ key: 'Key', value: 'value2' }, { key: 'Key2', value: 'value3' }]); mb.setHeader({ key: 'value4', key2: 'value5' }); expect(mb._headers).to.deep.equal([{ key: 'Key', value: 'value4' }, { key: 'Key2', value: 'value5' }]); }); it('should set multiple headers with the same key', function () { var mb = new Buildmail(); mb.setHeader('key', ['value1', 'value2', 'value3']); expect(mb._headers).to.deep.equal([{ key: 'Key', value: ['value1', 'value2', 'value3'] }]); }); }); describe('#addHeader', function () { it('should add header', function () { var mb = new Buildmail(); mb.addHeader('key', 'value1'); mb.addHeader('key', 'value2'); mb.addHeader([{ key: 'key', value: 'value2' }, { key: 'key2', value: 'value3' }]); mb.addHeader({ key: 'value4', key2: 'value5' }); expect(mb._headers).to.deep.equal([{ key: 'Key', value: 'value1' }, { key: 'Key', value: 'value2' }, { key: 'Key', value: 'value2' }, { key: 'Key2', value: 'value3' }, { key: 'Key', value: 'value4' }, { key: 'Key2', value: 'value5' }]); }); it('should set multiple headers with the same key', function () { var mb = new Buildmail(); mb.addHeader('key', ['value1', 'value2', 'value3']); expect(mb._headers).to.deep.equal([{ key: 'Key', value: 'value1' }, { key: 'Key', value: 'value2' }, { key: 'Key', value: 'value3' }]); }); }); describe('#getHeader', function () { it('should return first matching header value', function () { var mb = new Buildmail(); mb._headers = [{ key: 'Key', value: 'value4' }, { key: 'Key2', value: 'value5' }]; expect(mb.getHeader('KEY')).to.equal('value4'); }); }); describe('#setContent', function () { it('should set the contents for a node', function () { var mb = new Buildmail(); mb.setContent('abc'); expect(mb.content).to.equal('abc'); }); }); describe('#build', function () { it('should build root node', function (done) { var mb = new Buildmail('text/plain'). setHeader({ date: '12345', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: text/plain\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'Content-Transfer-Encoding: 7bit\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'Hello world!'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should build child node', function (done) { var mb = new Buildmail('multipart/mixed'), childNode = mb.createChild('text/plain'). setContent('Hello world!'), expected = 'Content-Type: text/plain\r\n' + 'Content-Transfer-Encoding: 7bit\r\n' + '\r\n' + 'Hello world!'; childNode.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should build multipart node', function (done) { var mb = new Buildmail('multipart/mixed', { baseBoundary: 'test' }).setHeader({ date: '12345', 'message-id': '67890' }), expected = 'Content-Type: multipart/mixed; boundary="----sinikael-?=_1-test"\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + '------sinikael-?=_1-test\r\n' + 'Content-Type: text/plain\r\n' + 'Content-Transfer-Encoding: 7bit\r\n' + '\r\n' + 'Hello world!\r\n' + '------sinikael-?=_1-test--\r\n'; mb.createChild('text/plain').setContent('Hello world!'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should build root with generated headers', function (done) { var mb = new Buildmail('text/plain'); mb.hostname = 'abc'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Date:\s/m.test(msg)).to.be.true; expect(/^Message\-ID:\s/m.test(msg)).to.be.true; expect(/^MIME-Version: 1\.0$/m.test(msg)).to.be.true; done(); }); }); it('should not include bcc missing in output, but in envelope', function (done) { var mb = new Buildmail('text/plain'). setHeader({ from: 'sender@example.com', to: 'receiver@example.com', bcc: 'bcc@example.com' }); var envelope = mb.getEnvelope(); expect(envelope).to.deep.equal({ from: 'sender@example.com', to: ['receiver@example.com', 'bcc@example.com'] }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^From: sender@example.com$/m.test(msg)).to.be.true; expect(/^To: receiver@example.com$/m.test(msg)).to.be.true; expect(!/^Bcc:/m.test(msg)).to.be.true; done(); }); }); it('should include bcc missing in output and in envelope', function (done) { var mb = new Buildmail( 'text/plain', { keepBcc: true }). setHeader({ from: 'sender@example.com', to: 'receiver@example.com', bcc: 'bcc@example.com' }); var envelope = mb.getEnvelope(); expect(envelope).to.deep.equal({ from: 'sender@example.com', to: ['receiver@example.com', 'bcc@example.com'] }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^From: sender@example.com$/m.test(msg)).to.be.true; expect(/^To: receiver@example.com$/m.test(msg)).to.be.true; expect(/^Bcc: bcc@example.com$/m.test(msg)).to.be.true; done(); }); }); it('should use set envelope', function (done) { var mb = new Buildmail('text/plain'). setHeader({ from: 'sender@example.com', to: 'receiver@example.com', bcc: 'bcc@example.com' }).setEnvelope({ from: 'U Name, A Name ', to: 'B Name , c@c.c', bcc: 'P P P, ' }); var envelope = mb.getEnvelope(); expect(envelope).to.deep.equal({ from: 'a@a.a', to: ['b@b.b', 'c@c.c', 'u@u.u'] }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^From: sender@example.com$/m.test(msg)).to.be.true; expect(/^To: receiver@example.com$/m.test(msg)).to.be.true; expect(!/^Bcc:/m.test(msg)).to.be.true; done(); }); }); it('should have unicode subject', function (done) { var mb = new Buildmail('text/plain'). setHeader({ subject: 'jõgeval istus kägu metsas' }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Subject: =\?UTF-8\?Q\?j=C3=B5geval\?= istus =\?UTF-8\?Q\?k=C3=A4gu\?= metsas$/m.test(msg)).to.be.true; done(); }); }); it('should have unicode subject with strange characters', function (done) { var mb = new Buildmail('text/plain'). setHeader({ subject: 'ˆ¸ÁÌÓıÏˇÁÛ^¸\\ÁıˆÌÁÛØ^\\˜Û˝™ˇıÓ¸^\\˜fi^\\·\\˜Ø^£˜#fi^\\£fi^\\£fi^\\' }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg.match(/\bSubject: [^\r]*\r\n( [^\r]*\r\n)*/)[0]).to.equal('Subject: =?UTF-8?B?y4bCuMOBw4zDk8Sxw4/Lh8OBw5tewrhcw4HEscuG?=\r\n =?UTF-8?B?w4zDgcObw5heXMucw5vLneKEosuHxLHDk8K4Xlw=?=\r\n =?UTF-8?B?y5zvrIFeXMK3XMucw5hewqPLnCPvrIFeXMKj76yB?=\r\n =?UTF-8?B?XlzCo++sgV5c?=\r\n'); done(); }); }); it('should keep 7bit text as is', function (done) { var mb = new Buildmail('text/plain'). setContent('tere tere'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/\r\n\r\ntere tere$/.test(msg)).to.be.true; expect(/^Content-Type: text\/plain$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true; done(); }); }); it('should prefer base64', function (done) { var mb = new Buildmail('text/plain'). setHeader({ subject: 'õõõõ' }). setContent('õõõõõõõõ'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: base64$/m.test(msg)).to.be.true; expect(/^Subject: =\?UTF-8\?B\?w7XDtcO1w7U=\?=$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('w7XDtcO1w7XDtcO1w7XDtQ=='); done(); }); }); it('should force quoted-printable', function (done) { var mb = new Buildmail('text/plain', { textEncoding: 'quoted-printable' }).setHeader({ subject: 'õõõõ' }). setContent('õõõõõõõõ'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; expect(/^Subject: =\?UTF-8\?Q\?=C3=B5=C3=B5=C3=B5=C3=B5\?=$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5'); done(); }); }); it('should prefer quoted-printable', function (done) { var mb = new Buildmail('text/plain'). setContent('ooooooooõ'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('oooooooo=C3=B5'); done(); }); }); it('should not flow text', function (done) { var mb = new Buildmail('text/plain'). setContent('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d=\r\n e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 =\r\n0'); done(); }); }); it('should not flow html', function (done) { var mb = new Buildmail('text/html'). setContent('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/html$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d=\r\n e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 =\r\n0'); done(); }); }); it('should use 7bit for html', function (done) { var mb = new Buildmail('text/html'). setContent('a b c d e f g h i j k l m o p\r\nq r s t u w x y z 1 2 3 4 5 6\r\n7 8 9 0 a b c d e f g h i j k\r\nl m o p q r s t u w x y z\r\n1 2 3 4 5 6 7 8 9 0'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/html$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true; msg = msg.split('\r\n\r\n'); msg.shift(); msg = msg.join('\r\n\r\n'); expect(msg).to.equal('a b c d e f g h i j k l m o p\r\nq r s t u w x y z 1 2 3 4 5 6\r\n7 8 9 0 a b c d e f g h i j k\r\nl m o p q r s t u w x y z\r\n1 2 3 4 5 6 7 8 9 0'); done(); }); }); it('should fetch ascii filename', function (done) { var mb = new Buildmail('text/plain', { filename: 'jogeva.txt' }). setContent('jogeva'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/\r\n\r\njogeva$/.test(msg)).to.be.true; expect(/^Content-Type: text\/plain; name=jogeva.txt$/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true; expect(/^Content-Disposition: attachment; filename=jogeva.txt$/m.test(msg)).to.be.true; done(); }); }); it('should set unicode filename', function (done) { var mb = new Buildmail('text/plain', { filename: 'jõgeva.txt' }). setContent('jõgeva'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain; charset=utf-8;/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; expect(/^Content-Disposition: attachment; filename\*0\*=utf-8''j%C3%B5geva.txt$/m.test(msg)).to.be.true; done(); }); }); it('should set dashed filename', function (done) { var mb = new Buildmail('text/plain', { filename: 'Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------.pdf' }). setContent('jõgeva'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg.indexOf('Content-Disposition: attachment;\r\n' + ' filename*0*=utf-8\'\'%C6%94------%C6%94------%C6%94------%C6%94;\r\n' + ' filename*1*=------%C6%94------%C6%94------%C6%94------.pdf')).to.be.gte(0); done(); }); }); it('should encode filename with a space', function (done) { var mb = new Buildmail('text/plain', { filename: 'document a.test.pdf' }). setContent('jõgeva'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: text\/plain; charset=utf-8;/m.test(msg)).to.be.true; expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true; expect(/^Content-Disposition: attachment; filename="document a.test.pdf"$/m.test(msg)).to.be.true; done(); }); }); it('should detect content type from filename', function (done) { var mb = new Buildmail(false, { filename: 'jogeva.zip' }). setContent('jogeva'); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Content-Type: application\/zip;/m.test(msg)).to.be.true; done(); }); }); it('should convert address objects', function (done) { var mb = new Buildmail(false). setHeader({ from: [{ name: 'the safewithme testuser', address: 'safewithme.testuser@jõgeva.com' }], cc: [{ name: 'the safewithme testuser', address: 'safewithme.testuser@jõgeva.com' }] }); expect(mb.getEnvelope()).to.deep.equal({ from: 'safewithme.testuser@xn--jgeva-dua.com', to: [ 'safewithme.testuser@xn--jgeva-dua.com' ] }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^From: the safewithme testuser $/m.test(msg)).to.be.true; expect(/^Cc: the safewithme testuser $/m.test(msg)).to.be.true; done(); }); }); it('should skip empty header', function (done) { var mb = new Buildmail('text/plain'). setHeader({ a: 'b', cc: '', dd: [], o: false, date: 'zzz', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: text/plain\r\n' + 'A: b\r\n' + 'Date: zzz\r\n' + 'Message-ID: <67890>\r\n' + 'Content-Transfer-Encoding: 7bit\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'Hello world!'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should not process prepared headers', function (done) { var mb = new Buildmail('text/plain'). setHeader({ unprepared: { value: new Array(100).join('a b') }, prepared: { value: new Array(100).join('a b'), prepared: true }, unicode: { value: 'õäöü', prepared: true }, date: 'zzz', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: text/plain\r\n' + // long folded value 'Unprepared: a ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' + ' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' + ' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' + ' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' + ' ba ba ba b\r\n' + // long unfolded value 'Prepared: a ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba b\r\n' + // non-ascii value 'Unicode: õäöü\r\n' + 'Date: zzz\r\n' + 'Message-ID: <67890>\r\n' + 'Content-Transfer-Encoding: 7bit\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'Hello world!'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should set default transfer encoding for application content', function (done) { var mb = new Buildmail('application/x-my-stuff'). setHeader({ date: '12345', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: application/x-my-stuff\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'Content-Transfer-Encoding: base64\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'SGVsbG8gd29ybGQh'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should not set transfer encoding for multipart content', function (done) { var mb = new Buildmail('multipart/global'). setHeader({ date: '12345', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: multipart/global; boundary=abc\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'Hello world!\r\n' + '--abc--' + '\r\n'; mb.boundary = 'abc'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should not set transfer encoding for message/ content', function (done) { var mb = new Buildmail('message/rfc822'). setHeader({ date: '12345', 'message-id': '67890' }). setContent('Hello world!'), expected = 'Content-Type: message/rfc822\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + 'Hello world!'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should use from domain for message-id', function (done) { var mb = new Buildmail('text/plain'). setHeader({ from: 'test@example.com' }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Message-ID: <[0-9a-f\-]+@example\.com>$/m.test(msg)).to.be.true; done(); }); }); it('should fallback to hostname for message-id', function (done) { var mb = new Buildmail('text/plain'); mb.hostname = 'abc'; mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^Message-ID: <[0-9a-f\-]+@abc>$/m.test(msg)).to.be.true; done(); }); }); }); describe('#getEnvelope', function () { it('should get envelope', function () { expect(new Buildmail().addHeader({ from: 'From ', sender: 'Sender ', to: 'receiver1@example.com' }).addHeader({ to: 'receiver2@example.com', cc: 'receiver1@example.com, receiver3@example.com', bcc: 'receiver4@example.com, Rec5 ' }).getEnvelope()).to.deep.equal({ from: 'from@example.com', to: ['receiver1@example.com', 'receiver2@example.com', 'receiver3@example.com', 'receiver4@example.com', 'receiver5@example.com'] }); expect(new Buildmail().addHeader({ sender: 'Sender ', to: 'receiver1@example.com' }).addHeader({ to: 'receiver2@example.com', cc: 'receiver1@example.com, receiver3@example.com', bcc: 'receiver4@example.com, Rec5 ' }).getEnvelope()).to.deep.equal({ from: 'sender@example.com', to: ['receiver1@example.com', 'receiver2@example.com', 'receiver3@example.com', 'receiver4@example.com', 'receiver5@example.com'] }); }); }); describe('#messageId', function () { it('should create and return message-Id', function () { var mail = new Buildmail().addHeader({ from: 'From ' }); var messageId = mail.messageId(); expect(/^<[\w\-]+@example\.com>$/.test(messageId)).to.be.true; expect(messageId).to.equal(mail.messageId()); }); }); describe('#getAddresses', function () { it('should get address object', function () { expect(new Buildmail().addHeader({ from: 'From ', sender: 'Sender ', to: 'receiver1@example.com' }).addHeader({ to: 'receiver2@example.com', cc: 'receiver1@example.com, receiver3@example.com', bcc: 'receiver4@example.com, Rec5 ' }).getAddresses()).to.deep.equal({ from: [{ address: 'from@example.com', name: 'From' }], sender: [{ address: 'sender@example.com', name: 'Sender' }], to: [{ address: 'receiver1@example.com', name: '' }, { address: 'receiver2@example.com', name: '' }], cc: [{ address: 'receiver1@example.com', name: '' }, { address: 'receiver3@example.com', name: '' }], bcc: [{ address: 'receiver4@example.com', name: '' }, { address: 'receiver5@example.com', name: 'Rec5' }] }); expect(new Buildmail().addHeader({ sender: 'Sender ', to: 'receiver1@example.com' }).addHeader({ to: 'receiver2@example.com', cc: 'receiver1@example.com, receiver1@example.com', bcc: 'receiver4@example.com, Rec5 ' }).getAddresses()).to.deep.equal({ sender: [{ address: 'sender@example.com', name: 'Sender' }], to: [{ address: 'receiver1@example.com', name: '' }, { address: 'receiver2@example.com', name: '' }], cc: [{ address: 'receiver1@example.com', name: '' }], bcc: [{ address: 'receiver4@example.com', name: '' }, { address: 'receiver5@example.com', name: 'Rec5' }] }); }); }); describe('#_parseAddresses', function () { it('should normalize header key', function () { var mb = new Buildmail(); expect(mb._parseAddresses('test address@example.com')).to.deep.equal([{ address: 'address@example.com', name: 'test' }]); expect(mb._parseAddresses(['test address@example.com'])).to.deep.equal([{ address: 'address@example.com', name: 'test' }]); expect(mb._parseAddresses([ ['test address@example.com'] ])).to.deep.equal([{ address: 'address@example.com', name: 'test' }]); expect(mb._parseAddresses([{ address: 'address@example.com', name: 'test' }])).to.deep.equal([{ address: 'address@example.com', name: 'test' }]); }); }); describe('#_normalizeHeaderKey', function () { it('should normalize header key', function () { var mb = new Buildmail(); expect(mb._normalizeHeaderKey('key')).to.equal('Key'); expect(mb._normalizeHeaderKey('mime-vERSION')).to.equal('MIME-Version'); expect(mb._normalizeHeaderKey('-a-long-name')).to.equal('-A-Long-Name'); expect(mb._normalizeHeaderKey('some-spf')).to.equal('Some-SPF'); expect(mb._normalizeHeaderKey('dkim-some')).to.equal('DKIM-Some'); expect(mb._normalizeHeaderKey('x-smtpapi')).to.equal('X-SMTPAPI'); expect(mb._normalizeHeaderKey('message-id')).to.equal('Message-ID'); expect(mb._normalizeHeaderKey('CONTENT-FEATUres')).to.equal('Content-features'); }); }); describe('#_handleContentType', function () { it('should do nothing on non multipart', function () { var mb = new Buildmail(); expect(mb.boundary).to.not.exist; mb._handleContentType({ value: 'text/plain' }); expect(mb.boundary).to.be.false; expect(mb.multipart).to.be.false; }); it('should use provided boundary', function () { var mb = new Buildmail(); expect(mb.boundary).to.not.exist; mb._handleContentType({ value: 'multipart/mixed', params: { boundary: 'abc' } }); expect(mb.boundary).to.equal('abc'); expect(mb.multipart).to.equal('mixed'); }); it('should generate boundary', function () { var mb = new Buildmail(); sinon.stub(mb, '_generateBoundary').returns('def'); expect(mb.boundary).to.not.exist; mb._handleContentType({ value: 'multipart/mixed', params: {} }); expect(mb.boundary).to.equal('def'); expect(mb.multipart).to.equal('mixed'); mb._generateBoundary.restore(); }); }); describe('#_generateBoundary ', function () { it('should genereate boundary string', function () { var mb = new Buildmail(); mb._nodeId = 'abc'; mb.rootNode.baseBoundary = 'def'; expect(mb._generateBoundary()).to.equal('----sinikael-?=_abc-def'); }); }); describe('#_encodeHeaderValue', function () { it('should do noting if possible', function () { var mb = new Buildmail(); expect(mb._encodeHeaderValue('x-my', 'test value')).to.equal('test value'); }); it('should encode non ascii characters', function () { var mb = new Buildmail(); expect(mb._encodeHeaderValue('x-my', 'test jõgeva value')).to.equal('test =?UTF-8?Q?j=C3=B5geva?= value'); }); it('should format references', function () { var mb = new Buildmail(); expect(mb._encodeHeaderValue('references', 'abc def')).to.equal(' '); expect(mb._encodeHeaderValue('references', ['abc', 'def'])).to.equal(' '); }); it('should format message-id', function () { var mb = new Buildmail(); expect(mb._encodeHeaderValue('message-id', 'abc')).to.equal(''); }); it('should format addresses', function () { var mb = new Buildmail(); expect(mb._encodeHeaderValue('from', { name: 'the safewithme testuser', address: 'safewithme.testuser@jõgeva.com' })).to.equal('the safewithme testuser '); }); }); describe('#_convertAddresses', function () { it('should convert address object to a string', function () { var mb = new Buildmail(); expect(mb._convertAddresses([{ name: 'Jõgeva Ants', address: 'ants@jõgeva.ee' }, { name: 'Composers', group: [{ address: 'sebu@example.com', name: 'Bach, Sebastian' }, { address: 'mozart@example.com', name: 'Mozzie' }] }])).to.equal('=?UTF-8?Q?J=C3=B5geva_Ants?= , Composers:"Bach, Sebastian" , Mozzie ;'); }); it('should keep ascii name as is', function () { var mb = new Buildmail(); expect(mb._convertAddresses([{ name: 'O\'Vigala Sass', address: 'a@b.c' }])).to.equal('O\'Vigala Sass '); }); it('should include name in quotes for special symbols', function () { var mb = new Buildmail(); expect(mb._convertAddresses([{ name: 'Sass, Vigala', address: 'a@b.c' }])).to.equal('"Sass, Vigala" '); }); it('should escape quotes', function () { var mb = new Buildmail(); expect(mb._convertAddresses([{ name: '"Vigala Sass"', address: 'a@b.c' }])).to.equal('"\\"Vigala Sass\\"" '); }); it('should mime encode unicode names', function () { var mb = new Buildmail(); expect(mb._convertAddresses([{ name: '"Jõgeva Sass"', address: 'a@b.c' }])).to.equal('=?UTF-8?Q?=22J=C3=B5geva_Sass=22?= '); }); }); describe('#_generateMessageId', function(){ it('should generate uuid-looking message-id', function(){ var mb = new Buildmail(); var mid = mb._generateMessageId(); expect(/^<[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}@.*>/.test(mid)).to.be.true; }); }); describe('Attachment streaming', function () { var port = 10337; var server; beforeEach(function (done) { server = http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); var data = new Buffer(new Array(1024 + 1).join('ä'), 'utf-8'); var i = 0; var sendByte = function () { if (i >= data.length) { return res.end(); } res.write(new Buffer([data[i++]])); setImmediate(sendByte); }; sendByte(); }); server.listen(port, done); }); afterEach(function (done) { server.close(done); }); it('should pipe URL as an attachment', function (done) { var mb = new Buildmail('text/plain'). setContent({ href: 'http://localhost:' + port }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^=C3=A4/m.test(msg)).to.be.true; done(); }); }); it('should reject URL attachment', function (done) { var mb = new Buildmail('text/plain', {disableUrlAccess: true}). setContent({ href: 'http://localhost:' + port }); mb.build(function (err, msg) { expect(err).to.exist; expect(msg).to.not.exist; done(); }); }); it('should return an error on invalid url', function (done) { var mb = new Buildmail('text/plain'). setContent({ href: 'http://__should_not_exist:58888' }); mb.build(function (err) { expect(err).to.exist; done(); }); }); it('should pipe file as an attachment', function (done) { var mb = new Buildmail('application/octet-stream'). setContent({ path: __dirname + '/fixtures/attachment.bin' }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(/^w7VrdmEK$/m.test(msg)).to.be.true; done(); }); }); it('should reject file as an attachment', function (done) { var mb = new Buildmail('application/octet-stream', {disableFileAccess: true}). setContent({ path: __dirname + '/fixtures/attachment.bin' }); mb.build(function (err, msg) { expect(err).to.exist; expect(msg).to.not.exist; done(); }); }); it('should return an error on invalid file path', function (done) { var mb = new Buildmail('text/plain'). setContent({ href: '/ASfsdfsdf/Sdgsgdfg/SDFgdfgdfg' }); mb.build(function (err) { expect(err).to.exist; done(); }); }); it('should return a error for an errored stream', function (done) { var s = new PassThrough(); var mb = new Buildmail('text/plain'). setContent(s); s.write('abc'); s.emit('error', new Error('Stream error')); setTimeout(function () { mb.build(function (err) { expect(err).to.exist; done(); }); }, 100); }); it('should return a stream error', function (done) { var s = new PassThrough(); var mb = new Buildmail('text/plain'). setContent(s); mb.build(function (err) { expect(err).to.exist; done(); }); s.write('abc'); setTimeout(function () { s.emit('error', new Error('Stream error')); }, 100); }); }); describe('#transform', function () { it('should pipe through provided stream', function (done) { var mb = new Buildmail('text/plain'). setHeader({ date: '12345', 'message-id': '67890' }). setContent('Hello world!'); var expected = 'Content-Type:\ttext/plain\r\n' + 'Date:\t12345\r\n' + 'Message-ID:\t<67890>\r\n' + 'Content-Transfer-Encoding:\t7bit\r\n' + 'MIME-Version:\t1.0\r\n' + '\r\n' + 'Hello\tworld!'; // Transform stream that replaces all spaces with tabs var transform = new Transform(); transform._transform = function (chunk, encoding, done) { if (encoding !== 'buffer') { chunk = new Buffer(chunk, encoding); } for (var i = 0, len = chunk.length; i < len; i++) { if (chunk[i] === 0x20) { chunk[i] = 0x09; } } this.push(chunk); done(); }; mb.transform(transform); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); }); describe('Raw content', function () { it('should return pregenerated content', function (done) { var expected = new Array(100).join('Test\n'); var mb = new Buildmail('text/plain').setRaw(expected); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should return pregenerated content for a child node', function (done) { var expected = new Array(100).join('Test\n'); var mb = new Buildmail('multipart/mixed', { baseBoundary: 'test' }).setHeader({ date: '12345', 'message-id': '67890' }); var child = mb.createChild(); child.setRaw(expected); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal('Content-Type: multipart/mixed; boundary="----sinikael-?=_1-test"\r\n' + 'Date: 12345\r\n' + 'Message-ID: <67890>\r\n' + 'MIME-Version: 1.0\r\n' + '\r\n' + '------sinikael-?=_1-test\r\n' + expected + '\r\n' + '------sinikael-?=_1-test--\r\n'); done(); }); }); it('should return pregenerated content from a stream', function (done) { var expected = new Array(100).join('Test\n'); var raw = new PassThrough(); var mb = new Buildmail('text/plain').setRaw(raw); setImmediate(function () { raw.end(expected); }); mb.build(function (err, msg) { expect(err).to.not.exist; msg = msg.toString(); expect(msg).to.equal(expected); done(); }); }); it('should catch error from a raw stream 1', function (done) { var raw = new PassThrough(); var mb = new Buildmail('text/plain').setRaw(raw); raw.emit('error', new Error('Stream error')); mb.build(function (err) { expect(err).to.exist; done(); }); }); it('should catch error from a raw stream 2', function (done) { var raw = new PassThrough(); var mb = new Buildmail('text/plain').setRaw(raw); mb.build(function (err) { expect(err).to.exist; done(); }); setImmediate(function () { raw.emit('error', new Error('Stream error')); }); }); }); });