w-email 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/w-email.umd.js +2 -2
- package/dist/w-email.umd.js.map +1 -1
- package/docs/WEmail.mjs.html +389 -156
- package/docs/global.html +88 -17
- package/docs/index.html +1 -1
- package/package.json +4 -3
- package/src/WEmail.mjs +388 -155
- package/test/api-WEmail-smtp.test.mjs +388 -0
- package/test/tools/fakeSmtp.mjs +310 -0
- package/test/tools/genCert.mjs +101 -0
- package/test/unit-WEmail-opt.test.mjs +316 -0
- package/test/all.test.mjs +0 -10
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import nodemailer from 'nodemailer'
|
|
3
|
+
import WEmail from '../src/WEmail.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
//settle, 執行WEmail並記錄結果, 區分同步拋出(thrown)、resolve(fulfilled)與reject(rejected)
|
|
7
|
+
async function settle(opt) {
|
|
8
|
+
let p
|
|
9
|
+
try {
|
|
10
|
+
p = new WEmail(opt)
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return { state: 'thrown', value: err }
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
let v = await p
|
|
17
|
+
return { state: 'fulfilled', value: v }
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
return { state: 'rejected', value: err }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
let base = {
|
|
26
|
+
srcName: 'test name',
|
|
27
|
+
srcEmail: 'sender@example.com',
|
|
28
|
+
srcPW: 'pw-123',
|
|
29
|
+
srcHost: '127.0.0.1',
|
|
30
|
+
srcPort: 8025,
|
|
31
|
+
emTitle: 'test title',
|
|
32
|
+
emContent: '<div>test content</div>',
|
|
33
|
+
toEmails: 'to1@example.com',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
describe('unit-WEmail-opt', function() {
|
|
38
|
+
//spec(src/WEmail.mjs JSDoc): opt各欄位給予undefined或null時皆視為未給定而使用預設值;
|
|
39
|
+
//欄位型別或值不合規時, 不連線即reject錯誤訊息字串; 不論輸入為何皆回傳Promise而不同步拋出
|
|
40
|
+
|
|
41
|
+
//攔截nodemailer.createTransport: 與src/WEmail.mjs所import者為同一ESM預設匯出物件,
|
|
42
|
+
//記錄transport與mail選項並以stub回呼成功而不連線; mocha --parallel之worker會跨檔重用, 故每案後必還原
|
|
43
|
+
let calls = []
|
|
44
|
+
let createTransportOri = null
|
|
45
|
+
beforeEach(function() {
|
|
46
|
+
calls = []
|
|
47
|
+
createTransportOri = nodemailer.createTransport
|
|
48
|
+
nodemailer.createTransport = (transportOptions) => {
|
|
49
|
+
let call = { transportOptions, mailOptions: null }
|
|
50
|
+
calls.push(call)
|
|
51
|
+
return {
|
|
52
|
+
sendMail: (mailOptions, cb) => {
|
|
53
|
+
call.mailOptions = mailOptions
|
|
54
|
+
cb(null, { response: '250 stub response' })
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
afterEach(function() {
|
|
60
|
+
nodemailer.createTransport = createTransportOri
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('正向對照: 合法輸入時建立1個transport並resolve其回應', async function() {
|
|
64
|
+
//攔截若未生效, 下方「不建立連線」之斷言將恆真, 故先確認攔截生效
|
|
65
|
+
let r = await settle(base)
|
|
66
|
+
assert.strict.deepEqual(r, { state: 'fulfilled', value: '250 stub response' }) //spec: resolve回傳郵件伺服器回應字串
|
|
67
|
+
assert.strict.equal(calls.length, 1)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('輸入檢核失敗: reject錯誤訊息字串且不建立連線', function() {
|
|
71
|
+
|
|
72
|
+
let att = { filename: 'x.txt' }
|
|
73
|
+
|
|
74
|
+
//[案例id, 說明, opt, 預期reject字串]
|
|
75
|
+
let cases = [
|
|
76
|
+
['E0', 'srcEmail未給定', { ...base, srcEmail: undefined }, 'srcEmail is not email'],
|
|
77
|
+
['E1', 'srcEmail為null', { ...base, srcEmail: null }, 'srcEmail is not email'],
|
|
78
|
+
['E3', 'srcEmail非email', { ...base, srcEmail: 'not-an-email' }, 'srcEmail is not email'],
|
|
79
|
+
['E4', 'srcEmail為數字', { ...base, srcEmail: 123 }, 'srcEmail is not email'],
|
|
80
|
+
['N3', 'srcName為物件', { ...base, srcName: {} }, 'srcName is not string'],
|
|
81
|
+
['NB5', 'srcName為布林', { ...base, srcName: true }, 'srcName is not string'],
|
|
82
|
+
['W5', 'srcPW為物件', { ...base, srcPW: {} }, 'srcPW is not string'],
|
|
83
|
+
['W5b', 'srcPW為布林', { ...base, srcPW: true }, 'srcPW is not string'],
|
|
84
|
+
['H1', 'srcHost為空字串', { ...base, srcHost: '' }, 'srcHost is not effective string'],
|
|
85
|
+
['H4', 'srcHost為空白字串', { ...base, srcHost: ' ' }, 'srcHost is not effective string'],
|
|
86
|
+
['H5', 'srcHost為數字(原會使行程崩潰)', { ...base, srcHost: 123 }, 'srcHost is not effective string'],
|
|
87
|
+
['HB1', 'srcHost為true(原會使行程崩潰)', { ...base, srcHost: true }, 'srcHost is not effective string'],
|
|
88
|
+
['HB2', 'srcHost為物件(原會使行程崩潰)', { ...base, srcHost: {} }, 'srcHost is not effective string'],
|
|
89
|
+
['HB3', 'srcHost為陣列', { ...base, srcHost: ['127.0.0.1'] }, 'srcHost is not effective string'],
|
|
90
|
+
['HB5', 'srcHost為0(原會改連localhost)', { ...base, srcHost: 0 }, 'srcHost is not effective string'],
|
|
91
|
+
['HB6', 'srcHost為false(原會改連localhost)', { ...base, srcHost: false }, 'srcHost is not effective string'],
|
|
92
|
+
['P7', 'srcPort為空字串', { ...base, srcPort: '' }, 'srcPort is not valid port'],
|
|
93
|
+
['P2', 'srcPort為0', { ...base, srcPort: 0 }, 'srcPort is not valid port'],
|
|
94
|
+
['P3', 'srcPort為負數', { ...base, srcPort: -1 }, 'srcPort is not valid port'],
|
|
95
|
+
['P4', 'srcPort為65536', { ...base, srcPort: 65536 }, 'srcPort is not valid port'],
|
|
96
|
+
['P5', 'srcPort為小數', { ...base, srcPort: 8025.5 }, 'srcPort is not valid port'],
|
|
97
|
+
['P6', 'srcPort為非數字字串', { ...base, srcPort: 'abc' }, 'srcPort is not valid port'],
|
|
98
|
+
['PB1', 'srcPort為true(原會連到埠1)', { ...base, srcPort: true }, 'srcPort is not valid port'],
|
|
99
|
+
['Q5', 'srcPort為物件', { ...base, srcPort: {} }, 'srcPort is not valid port'],
|
|
100
|
+
['Q7', 'srcPort為陣列', { ...base, srcPort: [8025] }, 'srcPort is not valid port'],
|
|
101
|
+
['Q8', 'srcPort為Infinity', { ...base, srcPort: Infinity }, 'srcPort is not valid port'],
|
|
102
|
+
['S3', 'emTitle為物件', { ...base, emTitle: {} }, 'emTitle is not string'],
|
|
103
|
+
['SB1', 'emTitle為布林', { ...base, emTitle: true }, 'emTitle is not string'],
|
|
104
|
+
['C3', 'emContent為物件', { ...base, emContent: {} }, 'emContent is not string'],
|
|
105
|
+
['C5', 'emContent為內容物件{path}(原會讀取本機檔案)', { ...base, emContent: { path: './package.json' } }, 'emContent is not string'],
|
|
106
|
+
['M2', 'emAttachments為字串', { ...base, emAttachments: '/path/a.txt' }, 'emAttachments is not object or array'],
|
|
107
|
+
['M3', 'emAttachments為數字', { ...base, emAttachments: 123 }, 'emAttachments is not object or array'],
|
|
108
|
+
['M4', 'emAttachments為空物件', { ...base, emAttachments: {} }, 'emAttachments is not valid attachment'],
|
|
109
|
+
['M6', 'emAttachments含空物件', { ...base, emAttachments: [{}] }, 'emAttachments[0] is not valid attachment'],
|
|
110
|
+
['M7', '附件僅有filename無內容來源', { ...base, emAttachments: att }, 'emAttachments is not valid attachment'],
|
|
111
|
+
['M9', '附件path為空字串', { ...base, emAttachments: { filename: 'x.txt', path: '' } }, 'emAttachments is not valid attachment'],
|
|
112
|
+
['MB6', '附件href為空字串', { ...base, emAttachments: { filename: 'x.txt', href: '' } }, 'emAttachments is not valid attachment'],
|
|
113
|
+
['MB8', '附件path為false', { ...base, emAttachments: { path: false } }, 'emAttachments is not valid attachment'],
|
|
114
|
+
['M10', '附件content為數字', { ...base, emAttachments: { filename: 'x.txt', content: 123 } }, 'emAttachments is not valid attachment'],
|
|
115
|
+
['M11', '附件path為數字', { ...base, emAttachments: { filename: 'x.txt', path: 123 } }, 'emAttachments is not valid attachment'],
|
|
116
|
+
['M12', '附件filename為數字', { ...base, emAttachments: [{ filename: 'a.txt', content: 'a' }, { filename: 123, content: 'x' }] }, 'emAttachments[1] is not valid attachment'],
|
|
117
|
+
['T2', 'toEmails為數字', { ...base, toEmails: 123 }, 'toEmails is not string or array'],
|
|
118
|
+
['T3', 'toEmails為位址物件', { ...base, toEmails: { name: 'A', address: 'a@example.com' } }, 'toEmails is not string or array'],
|
|
119
|
+
['A08', 'toEmails為逗號串接', { ...base, toEmails: 'a@example.com,b@example.com' }, 'toEmails is not email'],
|
|
120
|
+
['TB1', 'toEmails為分號串接', { ...base, toEmails: 'a@example.com;b@example.com' }, 'toEmails is not email'],
|
|
121
|
+
['TB3', 'toEmails為空白分隔(原會遺失收件人)', { ...base, toEmails: 'a@example.com b@example.com' }, 'toEmails is not email'],
|
|
122
|
+
['A26', 'toEmails帶顯示名稱', { ...base, toEmails: 'Alice <alice@example.com>' }, 'toEmails is not email'],
|
|
123
|
+
['A27', 'toEmails帶註解', { ...base, toEmails: 'bob@example.com (Bob)' }, 'toEmails is not email'],
|
|
124
|
+
['A28', 'toEmails為引號local-part', { ...base, toEmails: '"john..doe"@example.com' }, 'toEmails is not email'],
|
|
125
|
+
['A33', 'toEmails含CRLF與群組語法', { ...base, toEmails: 'x@example.com\r\nRCPT TO:<evil@example.com>' }, 'toEmails is not email'],
|
|
126
|
+
['TB4', 'toEmails無頂級網域', { ...base, toEmails: 'user@localhost' }, 'toEmails is not email'],
|
|
127
|
+
['TB6', 'toEmails之帳號含非ASCII', { ...base, toEmails: 'Ünïcödé@example.com' }, 'toEmails is not email'],
|
|
128
|
+
['TB12', 'toEmails之國際化網域含URL剖析會截斷之字元', { ...base, toEmails: 'user@例子.測試/evil.example' }, 'toEmails is not email'],
|
|
129
|
+
['T14', 'toEmails陣列含數字', { ...base, toEmails: [123] }, 'toEmails[0] is not email'],
|
|
130
|
+
['TB2', 'toEmails為巢狀陣列', { ...base, toEmails: [['a@example.com']] }, 'toEmails[0] is not email'],
|
|
131
|
+
['T15', 'toEmails陣列含位址物件', { ...base, toEmails: ['a@example.com', { name: 'B', address: 'b@example.com' }] }, 'toEmails[1] is not email'],
|
|
132
|
+
['CC1', 'toEmailsCC非email', { ...base, toEmailsCC: 'not-an-email' }, 'toEmailsCC is not email'],
|
|
133
|
+
['CC2', 'toEmailsCC為數字', { ...base, toEmailsCC: 123 }, 'toEmailsCC is not string or array'],
|
|
134
|
+
['BCC1', 'toEmailsBCC陣列含非email', { ...base, toEmailsBCC: ['ok@example.com', 'bad'] }, 'toEmailsBCC[1] is not email'],
|
|
135
|
+
['BCC2', 'toEmailsBCC為物件', { ...base, toEmailsBCC: {} }, 'toEmailsBCC is not string or array'],
|
|
136
|
+
['A07', '收件人皆為空陣列', { ...base, toEmails: [] }, 'no recipients'],
|
|
137
|
+
['T1', 'toEmails為null且無副本與密件(原會同步拋出)', { ...base, toEmails: null }, 'no recipients'],
|
|
138
|
+
['T7', 'toEmails為空白字串', { ...base, toEmails: ' ' }, 'no recipients'],
|
|
139
|
+
['T16', 'toEmails為false', { ...base, toEmails: false }, 'no recipients'],
|
|
140
|
+
['T17', 'toEmails陣列元素皆略過', { ...base, toEmails: [null, '', ' ', false, 0] }, 'no recipients'],
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
for (let [id, desc, opt, msg] of cases) {
|
|
144
|
+
it(`${id}: ${desc}`, async function() {
|
|
145
|
+
let r = await settle(opt)
|
|
146
|
+
assert.strict.deepEqual(r, { state: 'rejected', value: msg })
|
|
147
|
+
assert.strict.equal(calls.length, 0) //spec: 不連線即reject
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('異常值: 不同步拋出', function() {
|
|
154
|
+
|
|
155
|
+
//R-never-throw: 各欄位給予Symbol、null-prototype物件、BigInt或函式時, 皆reject錯誤訊息字串
|
|
156
|
+
let keys = ['srcName', 'srcEmail', 'srcPW', 'srcHost', 'srcPort', 'emTitle', 'emContent', 'toEmails', 'toEmailsCC', 'toEmailsBCC', 'emAttachments']
|
|
157
|
+
let values = [
|
|
158
|
+
['Symbol', Symbol('s')],
|
|
159
|
+
['null-prototype物件', Object.create(null)],
|
|
160
|
+
['BigInt', 10n],
|
|
161
|
+
['函式', () => 1],
|
|
162
|
+
]
|
|
163
|
+
for (let key of keys) {
|
|
164
|
+
it(`${key}: 異常值皆reject字串`, async function() {
|
|
165
|
+
for (let [name, v] of values) {
|
|
166
|
+
let opt = { ...base, [key]: v }
|
|
167
|
+
if (key === 'toEmailsCC' || key === 'toEmailsBCC') {
|
|
168
|
+
opt.toEmails = 'to1@example.com'
|
|
169
|
+
}
|
|
170
|
+
let r = await settle(opt)
|
|
171
|
+
assert.strict.equal(r.state, 'rejected', `${key}=${name}`)
|
|
172
|
+
assert.strict.equal(typeof r.value, 'string', `${key}=${name}`)
|
|
173
|
+
}
|
|
174
|
+
assert.strict.equal(calls.length, 0)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
it('T23/T24: 收件人陣列含Symbol或null-prototype物件時reject字串(原會同步拋出)', async function() {
|
|
179
|
+
for (let e of [Symbol('s'), Object.create(null)]) {
|
|
180
|
+
let r = await settle({ ...base, toEmails: [e] })
|
|
181
|
+
assert.strict.deepEqual(r, { state: 'rejected', value: 'toEmails[0] is not email' })
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('T1/T2/T3/T10/T11: 原會同步拋出之收件人輸入一律回傳Promise', async function() {
|
|
186
|
+
let opts = [
|
|
187
|
+
{ ...base, toEmails: null, toEmailsCC: 'cc@example.com' },
|
|
188
|
+
{ ...base, toEmails: 123 },
|
|
189
|
+
{ ...base, toEmails: { name: 'A', address: 'a@example.com' } },
|
|
190
|
+
{ ...base, toEmailsCC: null },
|
|
191
|
+
{ ...base, toEmailsBCC: null },
|
|
192
|
+
]
|
|
193
|
+
for (let opt of opts) {
|
|
194
|
+
let r = await settle(opt)
|
|
195
|
+
assert.strict.notEqual(r.state, 'thrown')
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('OB1: opt之getter拋錯時reject該錯誤而不同步拋出', async function() {
|
|
200
|
+
let opt = { ...base }
|
|
201
|
+
Object.defineProperty(opt, 'srcName', {
|
|
202
|
+
get() {
|
|
203
|
+
throw new Error('getter boom')
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
let r = await settle(opt)
|
|
207
|
+
assert.strict.equal(r.state, 'rejected')
|
|
208
|
+
assert.strict.ok(r.value instanceof Error) //spec: 輸入檢核以外之錯誤為Error物件
|
|
209
|
+
assert.strict.equal(r.value.message, 'getter boom')
|
|
210
|
+
assert.strict.equal(calls.length, 0)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe('transport選項', function() {
|
|
216
|
+
|
|
217
|
+
//[案例id, 說明, srcPort輸入, 預期port, 預期secure]; spec: srcPort可為正整數或其字串, 範圍1至65535, 465使用SSL/TLS連線, 預設587
|
|
218
|
+
let portCases = [
|
|
219
|
+
['P0', '未給定', undefined, 587, false],
|
|
220
|
+
['P8', 'null', null, 587, false],
|
|
221
|
+
['Q1', '數字587', 587, 587, false],
|
|
222
|
+
['Q1s', '字串587', '587', 587, false],
|
|
223
|
+
['Q2', '數字465', 465, 465, true],
|
|
224
|
+
['Q3', '字串465(原會以明文連TLS埠)', '465', 465, true],
|
|
225
|
+
['Q4', '前後空白之字串465', ' 465 ', 465, true],
|
|
226
|
+
['P1', '字串8025', '8025', 8025, false],
|
|
227
|
+
['QB1', '下界1', 1, 1, false],
|
|
228
|
+
['QB2', '上界65535', 65535, 65535, false],
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
for (let [id, desc, srcPort, port, secure] of portCases) {
|
|
232
|
+
it(`${id}: srcPort為${desc} → port ${port}、secure ${secure}`, async function() {
|
|
233
|
+
let r = await settle({ ...base, srcPort })
|
|
234
|
+
assert.strict.equal(r.state, 'fulfilled')
|
|
235
|
+
assert.strict.equal(calls[0].transportOptions.port, port)
|
|
236
|
+
assert.strict.equal(calls[0].transportOptions.secure, secure)
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
it('H0/H2/HB4: srcHost未給定或null用smtp.gmail.com, 前後空白去除後使用', async function() {
|
|
241
|
+
let exps = [
|
|
242
|
+
[undefined, 'smtp.gmail.com'],
|
|
243
|
+
[null, 'smtp.gmail.com'],
|
|
244
|
+
[' 127.0.0.1 ', '127.0.0.1'],
|
|
245
|
+
]
|
|
246
|
+
for (let [srcHost, host] of exps) {
|
|
247
|
+
calls = []
|
|
248
|
+
let r = await settle({ ...base, srcHost })
|
|
249
|
+
assert.strict.equal(r.state, 'fulfilled')
|
|
250
|
+
assert.strict.equal(calls[0].transportOptions.host, host)
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('W/E2: 登入帳號為去除前後空白之srcEmail, 密碼為srcPW, 未給定或null時為空字串, 數字轉為字串', async function() {
|
|
255
|
+
let exps = [
|
|
256
|
+
[{ srcEmail: ' sender@example.com ' }, { user: 'sender@example.com', pass: 'pw-123' }],
|
|
257
|
+
[{ srcPW: undefined }, { user: 'sender@example.com', pass: '' }],
|
|
258
|
+
[{ srcPW: null }, { user: 'sender@example.com', pass: '' }],
|
|
259
|
+
[{ srcPW: 0 }, { user: 'sender@example.com', pass: '0' }],
|
|
260
|
+
[{ srcPW: 123456 }, { user: 'sender@example.com', pass: '123456' }],
|
|
261
|
+
]
|
|
262
|
+
for (let [o, auth] of exps) {
|
|
263
|
+
calls = []
|
|
264
|
+
let r = await settle({ ...base, ...o })
|
|
265
|
+
assert.strict.equal(r.state, 'fulfilled')
|
|
266
|
+
assert.strict.deepEqual(calls[0].transportOptions.auth, auth)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('TLS: 不停用憑證驗證與STARTTLS', async function() {
|
|
271
|
+
//spec: 465使用SSL/TLS, 其他連接埠於伺服器支援時使用STARTTLS; 不得以tls.rejectUnauthorized:false或ignoreTLS停用
|
|
272
|
+
for (let srcPort of [465, 587]) {
|
|
273
|
+
calls = []
|
|
274
|
+
await settle({ ...base, srcPort })
|
|
275
|
+
let t = calls[0].transportOptions
|
|
276
|
+
assert.strict.equal(t.tls === undefined || t.tls.rejectUnauthorized !== false, true)
|
|
277
|
+
assert.strict.notEqual(t.ignoreTLS, true)
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe('通過檢核之輸入', function() {
|
|
284
|
+
|
|
285
|
+
it('文字欄位接受有限數字, emContent接受Buffer', async function() {
|
|
286
|
+
let opts = [
|
|
287
|
+
{ ...base, srcName: 123 },
|
|
288
|
+
{ ...base, emTitle: 123 },
|
|
289
|
+
{ ...base, emContent: 123 },
|
|
290
|
+
{ ...base, emContent: Buffer.from('<p>buffer</p>') },
|
|
291
|
+
]
|
|
292
|
+
for (let opt of opts) {
|
|
293
|
+
let r = await settle(opt)
|
|
294
|
+
assert.strict.equal(r.state, 'fulfilled')
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('A29/M8/R08b: 國際化網域、附件以href或raw給予、filename為false時通過檢核', async function() {
|
|
299
|
+
//href需實際下載故僅驗檢核, 寄送行為由api測試驗raw與國際化網域
|
|
300
|
+
let opts = [
|
|
301
|
+
{ ...base, toEmails: 'user@例子.測試' },
|
|
302
|
+
{ ...base, emAttachments: { filename: 'c.txt', href: 'https://example.com/c.txt' } },
|
|
303
|
+
{ ...base, emAttachments: { raw: 'Content-Type: text/plain\r\n\r\nd' } },
|
|
304
|
+
{ ...base, emAttachments: { filename: false, content: 'x' } },
|
|
305
|
+
]
|
|
306
|
+
for (let opt of opts) {
|
|
307
|
+
calls = []
|
|
308
|
+
let r = await settle(opt)
|
|
309
|
+
assert.strict.equal(r.state, 'fulfilled')
|
|
310
|
+
assert.strict.equal(calls.length, 1)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
})
|