Haraka 3.1.4 → 3.1.5
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/CONTRIBUTORS.md +1 -1
- package/Changes.md +14 -0
- package/package.json +8 -10
- package/plugins/queue/smtp_forward.js +4 -4
- package/run_tests +3 -15
- package/smtp_client.js +8 -6
- package/test/endpoint.js +5 -4
- package/test/host_pool.js +57 -31
- package/test/logger.js +75 -135
- package/test/outbound/bounce_net_errors.js +87 -131
- package/test/outbound/bounce_rfc3464.js +177 -254
- package/test/plugins/auth/auth_base.js +39 -44
- package/test/plugins/auth/auth_vpopmaild.js +8 -9
- package/test/plugins/queue/smtp_forward.js +953 -183
- package/test/plugins/rcpt_to.host_list_base.js +58 -93
- package/test/plugins/rcpt_to.in_host_list.js +126 -175
- package/test/plugins/record_envelope_addresses.js +8 -8
- package/test/plugins/status.js +10 -10
- package/test/plugins/tls.js +9 -19
- package/test/plugins/xclient.js +75 -110
- package/test/plugins.js +10 -13
- package/test/rfc1869.js +50 -70
- package/test/server.js +281 -436
- package/test/smtp_client.js +1192 -218
- package/test/tls_socket.js +104 -0
- package/tls_socket.js +16 -20
|
@@ -25,279 +25,202 @@ const queue_dir = path.resolve(__dirname, '../test-queue')
|
|
|
25
25
|
|
|
26
26
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
27
27
|
|
|
28
|
-
const ensureQueueDir = () =>
|
|
28
|
+
const ensureQueueDir = () => fs.promises.mkdir(queue_dir, { recursive: true })
|
|
29
|
+
|
|
30
|
+
const cleanQueueDir = async () => {
|
|
31
|
+
if (!fs.existsSync(queue_dir)) return
|
|
32
|
+
for (const file of fs.readdirSync(queue_dir)) {
|
|
33
|
+
const full = path.resolve(queue_dir, file)
|
|
34
|
+
if (fs.lstatSync(full).isDirectory()) throw new Error(`unexpected subdirectory: ${full}`)
|
|
35
|
+
fs.unlinkSync(full)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mockHMailItem = (ctx, opts = {}) =>
|
|
29
40
|
new Promise((resolve, reject) => {
|
|
30
|
-
|
|
31
|
-
if (exists) return resolve()
|
|
32
|
-
fs.mkdir(queue_dir, (err) => (err ? reject(err) : resolve()))
|
|
33
|
-
})
|
|
41
|
+
util_hmailitem.newMockHMailItem(ctx, reject, opts, resolve)
|
|
34
42
|
})
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
/** Spies on HMailItem.prototype.temp_fail and resolves when called. */
|
|
45
|
+
const interceptTempFail = (mock_hmail, mock_socket, assertion, conversation) =>
|
|
37
46
|
new Promise((resolve, reject) => {
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
const orig = HMailItem.prototype.temp_fail
|
|
48
|
+
HMailItem.prototype.temp_fail = function () {
|
|
40
49
|
try {
|
|
41
|
-
|
|
42
|
-
const full = path.resolve(queue_dir, file)
|
|
43
|
-
if (fs.lstatSync(full).isDirectory()) return reject(new Error(`unexpected subdirectory: ${full}`))
|
|
44
|
-
fs.unlinkSync(full)
|
|
45
|
-
}
|
|
50
|
+
assertion(this)
|
|
46
51
|
resolve()
|
|
47
|
-
} catch (
|
|
48
|
-
reject(
|
|
52
|
+
} catch (e) {
|
|
53
|
+
reject(e)
|
|
54
|
+
} finally {
|
|
55
|
+
HMailItem.prototype.temp_fail = orig
|
|
49
56
|
}
|
|
50
|
-
}
|
|
57
|
+
}
|
|
58
|
+
util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, reject, conversation, () => {})
|
|
51
59
|
})
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
/** Spies on outbound.send_email and resolves when called. */
|
|
62
|
+
const interceptSendEmail = (mock_hmail, mock_socket, assertion, conversation) =>
|
|
54
63
|
new Promise((resolve, reject) => {
|
|
55
|
-
|
|
64
|
+
const orig = outbound_context.exports.send_email
|
|
65
|
+
outbound_context.exports.send_email = (from, to, contents) => {
|
|
66
|
+
try {
|
|
67
|
+
assertion(contents)
|
|
68
|
+
resolve()
|
|
69
|
+
} catch (e) {
|
|
70
|
+
reject(e)
|
|
71
|
+
} finally {
|
|
72
|
+
outbound_context.exports.send_email = orig
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, reject, conversation, () => {})
|
|
56
76
|
})
|
|
57
77
|
|
|
78
|
+
// ── Shared conversation building blocks ───────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const EHLO_PREAMBLE = [
|
|
81
|
+
{ from: 'remote', line: '220 testing-smtp' },
|
|
82
|
+
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
83
|
+
{ from: 'remote', line: '220-testing-smtp' },
|
|
84
|
+
{ from: 'remote', line: '220 8BITMIME' },
|
|
85
|
+
]
|
|
86
|
+
const MAIL_OK = [
|
|
87
|
+
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
88
|
+
{ from: 'remote', line: '250 2.1.0 Ok' },
|
|
89
|
+
]
|
|
90
|
+
const RCPT_OK = [
|
|
91
|
+
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
92
|
+
{ from: 'remote', line: '250 2.1.5 Ok' },
|
|
93
|
+
]
|
|
94
|
+
const QUIT = { from: 'haraka', test: 'QUIT', end_test: true }
|
|
95
|
+
|
|
58
96
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
59
97
|
|
|
98
|
+
// Permanent bounce tests: spy on send_email, check DSN bounce contents
|
|
99
|
+
const bounceCases = [
|
|
100
|
+
{
|
|
101
|
+
name: 'MAIL FROM 500 triggers RFC3464 bounce with status 5.0.0',
|
|
102
|
+
statusRe: /^Status: 5\.0\.0/m,
|
|
103
|
+
messageRe: /Absolutely not acceptable\. Basic Test Only\./,
|
|
104
|
+
conversation: [
|
|
105
|
+
...EHLO_PREAMBLE,
|
|
106
|
+
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
107
|
+
{ from: 'remote', line: '500 5.0.0 Absolutely not acceptable. Basic Test Only.' },
|
|
108
|
+
QUIT,
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'RCPT-TO 5XX triggers RFC3464 bounce with status 5.1.1',
|
|
113
|
+
statusRe: /^Status: 5\.1\.1/m,
|
|
114
|
+
messageRe: /Not available and will not come back/,
|
|
115
|
+
conversation: [
|
|
116
|
+
...EHLO_PREAMBLE,
|
|
117
|
+
...MAIL_OK,
|
|
118
|
+
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
119
|
+
{ from: 'remote', line: '550 5.1.1 Not available and will not come back' },
|
|
120
|
+
QUIT,
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'DATA 5XX triggers RFC3464 bounce with status 5.6.0',
|
|
125
|
+
statusRe: /^Status: 5\.6\.0/m,
|
|
126
|
+
messageRe: /I never did and will like ascii art cats/,
|
|
127
|
+
conversation: [
|
|
128
|
+
...EHLO_PREAMBLE,
|
|
129
|
+
...MAIL_OK,
|
|
130
|
+
...RCPT_OK,
|
|
131
|
+
{ from: 'haraka', test: 'DATA' },
|
|
132
|
+
{ from: 'remote', line: '550 5.6.0 I never did and will like ascii art cats.' },
|
|
133
|
+
QUIT,
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
// Temporary failure tests: spy on temp_fail, check DSN rcpt fields
|
|
139
|
+
const tempFailCases = [
|
|
140
|
+
{
|
|
141
|
+
name: 'early 3XX response triggers temp_fail with status 3.0.0',
|
|
142
|
+
dsn_status: '3.0.0',
|
|
143
|
+
dsn_action: 'delayed',
|
|
144
|
+
smtpRe: /No time for you right now/,
|
|
145
|
+
conversation: [
|
|
146
|
+
...EHLO_PREAMBLE,
|
|
147
|
+
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
148
|
+
{ from: 'remote', line: '300 3.0.0 No time for you right now' },
|
|
149
|
+
QUIT,
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'RCPT-TO 4XX triggers temp_fail with status 4.0.0',
|
|
154
|
+
dsn_status: '4.0.0',
|
|
155
|
+
dsn_action: 'delayed',
|
|
156
|
+
smtpRe: /Currently not available\. Try again later\./,
|
|
157
|
+
conversation: [
|
|
158
|
+
...EHLO_PREAMBLE,
|
|
159
|
+
...MAIL_OK,
|
|
160
|
+
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
161
|
+
{ from: 'remote', line: '400 4.0.0 Currently not available. Try again later.' },
|
|
162
|
+
QUIT,
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'DATA 4XX triggers temp_fail with status 4.6.0',
|
|
167
|
+
dsn_status: '4.6.0',
|
|
168
|
+
dsn_action: 'delayed',
|
|
169
|
+
smtpRe: /Currently I do not like ascii art cats\./,
|
|
170
|
+
conversation: [
|
|
171
|
+
...EHLO_PREAMBLE,
|
|
172
|
+
...MAIL_OK,
|
|
173
|
+
...RCPT_OK,
|
|
174
|
+
{ from: 'haraka', test: 'DATA' },
|
|
175
|
+
{ from: 'remote', line: '450 4.6.0 Currently I do not like ascii art cats.' },
|
|
176
|
+
QUIT,
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
]
|
|
180
|
+
|
|
60
181
|
describe('outbound_bounce_rfc3464', () => {
|
|
61
182
|
beforeEach(ensureQueueDir)
|
|
62
183
|
afterEach(cleanQueueDir)
|
|
63
184
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
util_hmailitem.playTestSmtpConversation(
|
|
86
|
-
mock_hmail,
|
|
87
|
-
mock_socket,
|
|
88
|
-
reject,
|
|
89
|
-
[
|
|
90
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
91
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
92
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
93
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
94
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
95
|
-
{ from: 'remote', line: '500 5.0.0 Absolutely not acceptable. Basic Test Only.' },
|
|
96
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
97
|
-
],
|
|
98
|
-
() => {},
|
|
99
|
-
)
|
|
100
|
-
})
|
|
185
|
+
describe('permanent bounce (send_email)', () => {
|
|
186
|
+
for (const { name, statusRe, messageRe, conversation } of bounceCases) {
|
|
187
|
+
it(name, async () => {
|
|
188
|
+
const mock_hmail = await mockHMailItem(outbound_context)
|
|
189
|
+
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
190
|
+
mock_socket.writable = true
|
|
191
|
+
await interceptSendEmail(
|
|
192
|
+
mock_hmail,
|
|
193
|
+
mock_socket,
|
|
194
|
+
(contents) => {
|
|
195
|
+
assert.match(contents, /^Content-type: message\/delivery-status/m)
|
|
196
|
+
assert.match(contents, /^Final-Recipient: rfc822;recipient@domain/m)
|
|
197
|
+
assert.match(contents, /^Action: failed/m)
|
|
198
|
+
assert.match(contents, statusRe)
|
|
199
|
+
assert.match(contents, messageRe)
|
|
200
|
+
},
|
|
201
|
+
conversation,
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
101
205
|
})
|
|
102
206
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
util_hmailitem.playTestSmtpConversation(
|
|
123
|
-
mock_hmail,
|
|
124
|
-
mock_socket,
|
|
125
|
-
reject,
|
|
126
|
-
[
|
|
127
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
128
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
129
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
130
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
131
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
132
|
-
{ from: 'remote', line: '300 3.0.0 No time for you right now' },
|
|
133
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
134
|
-
],
|
|
135
|
-
() => {},
|
|
136
|
-
)
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('RCPT-TO 4XX triggers temp_fail with status 4.0.0', async () => {
|
|
141
|
-
const mock_hmail = await mockHMailItem(outbound_context)
|
|
142
|
-
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
143
|
-
mock_socket.writable = true
|
|
144
|
-
|
|
145
|
-
await new Promise((resolve, reject) => {
|
|
146
|
-
const orig = HMailItem.prototype.temp_fail
|
|
147
|
-
HMailItem.prototype.temp_fail = function (err, opts) {
|
|
148
|
-
try {
|
|
149
|
-
assert.equal(this.todo.rcpt_to[0].dsn_status, '4.0.0')
|
|
150
|
-
assert.equal(this.todo.rcpt_to[0].dsn_action, 'delayed')
|
|
151
|
-
assert.match(this.todo.rcpt_to[0].dsn_smtp_response, /Currently not available\. Try again later\./)
|
|
152
|
-
resolve()
|
|
153
|
-
} catch (e) {
|
|
154
|
-
reject(e)
|
|
155
|
-
} finally {
|
|
156
|
-
HMailItem.prototype.temp_fail = orig
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
util_hmailitem.playTestSmtpConversation(
|
|
160
|
-
mock_hmail,
|
|
161
|
-
mock_socket,
|
|
162
|
-
reject,
|
|
163
|
-
[
|
|
164
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
165
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
166
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
167
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
168
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
169
|
-
{ from: 'remote', line: '250 2.1.0 Ok' },
|
|
170
|
-
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
171
|
-
{ from: 'remote', line: '400 4.0.0 Currently not available. Try again later.' },
|
|
172
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
173
|
-
],
|
|
174
|
-
() => {},
|
|
175
|
-
)
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('DATA 4XX triggers temp_fail with status 4.6.0', async () => {
|
|
180
|
-
const mock_hmail = await mockHMailItem(outbound_context)
|
|
181
|
-
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
182
|
-
mock_socket.writable = true
|
|
183
|
-
|
|
184
|
-
await new Promise((resolve, reject) => {
|
|
185
|
-
const orig = HMailItem.prototype.temp_fail
|
|
186
|
-
HMailItem.prototype.temp_fail = function (err, opts) {
|
|
187
|
-
try {
|
|
188
|
-
assert.equal(this.todo.rcpt_to[0].dsn_status, '4.6.0')
|
|
189
|
-
assert.equal(this.todo.rcpt_to[0].dsn_action, 'delayed')
|
|
190
|
-
assert.match(this.todo.rcpt_to[0].dsn_smtp_response, /Currently I do not like ascii art cats\./)
|
|
191
|
-
resolve()
|
|
192
|
-
} catch (e) {
|
|
193
|
-
reject(e)
|
|
194
|
-
} finally {
|
|
195
|
-
HMailItem.prototype.temp_fail = orig
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
util_hmailitem.playTestSmtpConversation(
|
|
199
|
-
mock_hmail,
|
|
200
|
-
mock_socket,
|
|
201
|
-
reject,
|
|
202
|
-
[
|
|
203
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
204
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
205
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
206
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
207
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
208
|
-
{ from: 'remote', line: '250 2.1.0 Ok' },
|
|
209
|
-
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
210
|
-
{ from: 'remote', line: '250 2.1.5 Ok' },
|
|
211
|
-
{ from: 'haraka', test: 'DATA' },
|
|
212
|
-
{ from: 'remote', line: '450 4.6.0 Currently I do not like ascii art cats.' },
|
|
213
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
214
|
-
],
|
|
215
|
-
() => {},
|
|
216
|
-
)
|
|
217
|
-
})
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it('RCPT-TO 5XX triggers RFC3464 bounce with status 5.1.1', async () => {
|
|
221
|
-
const mock_hmail = await mockHMailItem(outbound_context)
|
|
222
|
-
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
223
|
-
mock_socket.writable = true
|
|
224
|
-
|
|
225
|
-
await new Promise((resolve, reject) => {
|
|
226
|
-
const orig = outbound_context.exports.send_email
|
|
227
|
-
outbound_context.exports.send_email = (from, to, contents, cb, opts) => {
|
|
228
|
-
try {
|
|
229
|
-
assert.match(contents, /^Content-type: message\/delivery-status/m)
|
|
230
|
-
assert.match(contents, /^Final-Recipient: rfc822;recipient@domain/m)
|
|
231
|
-
assert.match(contents, /^Action: failed/m)
|
|
232
|
-
assert.match(contents, /^Status: 5\.1\.1/m)
|
|
233
|
-
assert.match(contents, /Not available and will not come back/)
|
|
234
|
-
resolve()
|
|
235
|
-
} catch (e) {
|
|
236
|
-
reject(e)
|
|
237
|
-
} finally {
|
|
238
|
-
outbound_context.exports.send_email = orig
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
util_hmailitem.playTestSmtpConversation(
|
|
242
|
-
mock_hmail,
|
|
243
|
-
mock_socket,
|
|
244
|
-
reject,
|
|
245
|
-
[
|
|
246
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
247
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
248
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
249
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
250
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
251
|
-
{ from: 'remote', line: '250 2.1.0 Ok' },
|
|
252
|
-
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
253
|
-
{ from: 'remote', line: '550 5.1.1 Not available and will not come back' },
|
|
254
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
255
|
-
],
|
|
256
|
-
() => {},
|
|
257
|
-
)
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('DATA 5XX triggers RFC3464 bounce with status 5.6.0', async () => {
|
|
262
|
-
const mock_hmail = await mockHMailItem(outbound_context)
|
|
263
|
-
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
264
|
-
mock_socket.writable = true
|
|
265
|
-
|
|
266
|
-
await new Promise((resolve, reject) => {
|
|
267
|
-
const orig = outbound_context.exports.send_email
|
|
268
|
-
outbound_context.exports.send_email = (from, to, contents, cb, opts) => {
|
|
269
|
-
try {
|
|
270
|
-
assert.match(contents, /^Content-type: message\/delivery-status/m)
|
|
271
|
-
assert.match(contents, /^Final-Recipient: rfc822;recipient@domain/m)
|
|
272
|
-
assert.match(contents, /^Action: failed/m)
|
|
273
|
-
assert.match(contents, /^Status: 5\.6\.0/m)
|
|
274
|
-
assert.match(contents, /I never did and will like ascii art cats/)
|
|
275
|
-
resolve()
|
|
276
|
-
} catch (e) {
|
|
277
|
-
reject(e)
|
|
278
|
-
} finally {
|
|
279
|
-
outbound_context.exports.send_email = orig
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
util_hmailitem.playTestSmtpConversation(
|
|
283
|
-
mock_hmail,
|
|
284
|
-
mock_socket,
|
|
285
|
-
reject,
|
|
286
|
-
[
|
|
287
|
-
{ from: 'remote', line: '220 testing-smtp' },
|
|
288
|
-
{ from: 'haraka', test: (l) => l.match(/^EHLO /), description: 'EHLO' },
|
|
289
|
-
{ from: 'remote', line: '220-testing-smtp' },
|
|
290
|
-
{ from: 'remote', line: '220 8BITMIME' },
|
|
291
|
-
{ from: 'haraka', test: 'MAIL FROM:<sender@domain>' },
|
|
292
|
-
{ from: 'remote', line: '250 2.1.0 Ok' },
|
|
293
|
-
{ from: 'haraka', test: 'RCPT TO:<recipient@domain>' },
|
|
294
|
-
{ from: 'remote', line: '250 2.1.5 Ok' },
|
|
295
|
-
{ from: 'haraka', test: 'DATA' },
|
|
296
|
-
{ from: 'remote', line: '550 5.6.0 I never did and will like ascii art cats.' },
|
|
297
|
-
{ from: 'haraka', test: 'QUIT', end_test: true },
|
|
298
|
-
],
|
|
299
|
-
() => {},
|
|
300
|
-
)
|
|
301
|
-
})
|
|
207
|
+
describe('temporary failure (temp_fail)', () => {
|
|
208
|
+
for (const { name, dsn_status, dsn_action, smtpRe, conversation } of tempFailCases) {
|
|
209
|
+
it(name, async () => {
|
|
210
|
+
const mock_hmail = await mockHMailItem(outbound_context)
|
|
211
|
+
const mock_socket = mock_sock.connect('testhost', 'testport')
|
|
212
|
+
mock_socket.writable = true
|
|
213
|
+
await interceptTempFail(
|
|
214
|
+
mock_hmail,
|
|
215
|
+
mock_socket,
|
|
216
|
+
(h) => {
|
|
217
|
+
assert.equal(h.todo.rcpt_to[0].dsn_status, dsn_status)
|
|
218
|
+
assert.equal(h.todo.rcpt_to[0].dsn_action, dsn_action)
|
|
219
|
+
assert.match(h.todo.rcpt_to[0].dsn_smtp_response, smtpRe)
|
|
220
|
+
},
|
|
221
|
+
conversation,
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
}
|
|
302
225
|
})
|
|
303
226
|
})
|