Haraka 3.1.3 → 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.
Files changed (65) hide show
  1. package/.prettierignore +2 -0
  2. package/CONTRIBUTORS.md +23 -1
  3. package/Changes.md +52 -0
  4. package/Plugins.md +81 -64
  5. package/README.md +1 -1
  6. package/bin/haraka +7 -5
  7. package/connection.js +15 -19
  8. package/docs/Plugins.md +1 -1
  9. package/docs/plugins/aliases.md +0 -2
  10. package/docs/plugins/queue/qmail-queue.md +0 -1
  11. package/logger.js +2 -2
  12. package/outbound/hmail.js +76 -83
  13. package/outbound/index.js +36 -34
  14. package/outbound/queue.js +231 -176
  15. package/package.json +26 -29
  16. package/plugins/prevent_credential_leaks.js +2 -2
  17. package/plugins/process_title.js +1 -1
  18. package/plugins/queue/smtp_forward.js +5 -5
  19. package/plugins/status.js +8 -5
  20. package/plugins/tls.js +1 -1
  21. package/plugins.js +19 -14
  22. package/rfc1869.js +10 -10
  23. package/run_tests +8 -2
  24. package/server.js +15 -10
  25. package/smtp_client.js +10 -15
  26. package/test/config/tls/haraka.local.pem +47 -47
  27. package/test/connection.js +286 -147
  28. package/test/endpoint.js +5 -4
  29. package/test/fixtures/line_socket.js +1 -0
  30. package/test/fixtures/util_hmailitem.js +1 -1
  31. package/test/host_pool.js +57 -31
  32. package/test/logger.js +75 -135
  33. package/test/outbound/bounce_net_errors.js +132 -0
  34. package/test/outbound/bounce_rfc3464.js +226 -0
  35. package/test/outbound/hmail.js +140 -104
  36. package/test/outbound/index.js +61 -101
  37. package/test/outbound/qfile.js +25 -25
  38. package/test/outbound/queue.js +233 -0
  39. package/test/plugins/auth/auth_base.js +39 -44
  40. package/test/plugins/auth/auth_vpopmaild.js +8 -9
  41. package/test/plugins/queue/smtp_forward.js +953 -183
  42. package/test/plugins/rcpt_to.host_list_base.js +58 -93
  43. package/test/plugins/rcpt_to.in_host_list.js +126 -175
  44. package/test/plugins/record_envelope_addresses.js +93 -0
  45. package/test/plugins/status.js +10 -10
  46. package/test/plugins/tls.js +11 -21
  47. package/test/plugins/xclient.js +102 -0
  48. package/test/plugins.js +10 -13
  49. package/test/rfc1869.js +71 -48
  50. package/test/server.js +281 -436
  51. package/test/smtp_client.js +1194 -220
  52. package/test/tls_socket.js +74 -243
  53. package/test/transaction.js +486 -201
  54. package/tls_socket.js +19 -23
  55. package/transaction.js +33 -10
  56. package/config/rabbitmq.ini +0 -10
  57. package/config/rabbitmq_amqplib.ini +0 -19
  58. package/docs/plugins/queue/rabbitmq.md +0 -34
  59. package/docs/plugins/queue/rabbitmq_amqplib.md +0 -51
  60. package/plugins/queue/rabbitmq.js +0 -141
  61. package/plugins/queue/rabbitmq_amqplib.js +0 -96
  62. package/test/config/tls/ec.pem +0 -23
  63. package/test/config/tls/mismatched.pem +0 -49
  64. package/test/outbound_bounce_net_errors.js +0 -157
  65. package/test/outbound_bounce_rfc3464.js +0 -366
@@ -0,0 +1,226 @@
1
+ 'use strict'
2
+
3
+ // Testing bounce email contents related to errors occurring during SMTP dialog.
4
+ // These tests simulate a remote SMTP server responding with various error codes
5
+ // and verify that Haraka generates correctly formatted RFC 3464 DSN bounce messages.
6
+
7
+ const { describe, it, beforeEach, afterEach } = require('node:test')
8
+ const assert = require('node:assert')
9
+ const fs = require('node:fs')
10
+ const path = require('node:path')
11
+
12
+ // Load outbound/index FIRST to avoid the circular-dependency boot-order issue.
13
+ const outbound = require('../../outbound')
14
+ const HMailItem = outbound.HMailItem
15
+ const TODOItem = require('../../outbound/todo')
16
+ const obc = require('../../outbound/config')
17
+
18
+ const util_hmailitem = require('../fixtures/util_hmailitem')
19
+ const mock_sock = require('../fixtures/line_socket')
20
+
21
+ obc.cfg.pool_concurrency_max = 0
22
+
23
+ const outbound_context = { TODOItem, exports: outbound }
24
+ const queue_dir = path.resolve(__dirname, '../test-queue')
25
+
26
+ // ── Helpers ───────────────────────────────────────────────────────────────────
27
+
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 = {}) =>
40
+ new Promise((resolve, reject) => {
41
+ util_hmailitem.newMockHMailItem(ctx, reject, opts, resolve)
42
+ })
43
+
44
+ /** Spies on HMailItem.prototype.temp_fail and resolves when called. */
45
+ const interceptTempFail = (mock_hmail, mock_socket, assertion, conversation) =>
46
+ new Promise((resolve, reject) => {
47
+ const orig = HMailItem.prototype.temp_fail
48
+ HMailItem.prototype.temp_fail = function () {
49
+ try {
50
+ assertion(this)
51
+ resolve()
52
+ } catch (e) {
53
+ reject(e)
54
+ } finally {
55
+ HMailItem.prototype.temp_fail = orig
56
+ }
57
+ }
58
+ util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, reject, conversation, () => {})
59
+ })
60
+
61
+ /** Spies on outbound.send_email and resolves when called. */
62
+ const interceptSendEmail = (mock_hmail, mock_socket, assertion, conversation) =>
63
+ new Promise((resolve, reject) => {
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, () => {})
76
+ })
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
+
96
+ // ── Tests ─────────────────────────────────────────────────────────────────────
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
+
181
+ describe('outbound_bounce_rfc3464', () => {
182
+ beforeEach(ensureQueueDir)
183
+ afterEach(cleanQueueDir)
184
+
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
+ }
205
+ })
206
+
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
+ }
225
+ })
226
+ })
@@ -1,155 +1,191 @@
1
+ 'use strict'
2
+
3
+ const { describe, it, before, beforeEach, afterEach } = require('node:test')
1
4
  const assert = require('node:assert')
5
+ const { EventEmitter } = require('node:events')
2
6
  const fs = require('node:fs')
3
7
  const path = require('node:path')
4
8
 
5
- const Hmail = require('../../outbound/hmail')
6
- const outbound = require('../../outbound/index')
9
+ // Load outbound/index FIRST to avoid the circular-dependency boot-order issue.
10
+ const outbound = require('../../outbound')
11
+ const Hmail = outbound.HMailItem
12
+ const client_pool = require('../../outbound/client_pool')
13
+
14
+ // ── Helpers ───────────────────────────────────────────────────────────────────
15
+
16
+ const onEvent = (emitter, event) => new Promise((resolve) => emitter.once(event, resolve))
17
+
18
+ // ── Tests ─────────────────────────────────────────────────────────────────────
7
19
 
8
20
  describe('outbound/hmail', () => {
9
- beforeEach((done) => {
10
- this.hmail = new Hmail(
21
+ let hmail
22
+
23
+ beforeEach(() => {
24
+ hmail = new Hmail(
11
25
  '1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
12
26
  'test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
13
27
  {},
14
28
  )
15
- done()
16
29
  })
17
30
 
18
- it('sort_mx', (done) => {
19
- const sorted = this.hmail.sort_mx([
31
+ describe('socket error/timeout handler robustness (#3388)', () => {
32
+ const mx = { using_lmtp: false, port: 25, exchange: 'mx.example.com', bind: null, bind_helo: 'test' }
33
+ let origRelease
34
+
35
+ function makeSocket() {
36
+ const s = new EventEmitter()
37
+ s.name = 'mock'
38
+ s.writable = true
39
+ s.write = () => {}
40
+ s.destroy = () => {}
41
+ return s
42
+ }
43
+
44
+ beforeEach(() => {
45
+ origRelease = client_pool.release_client
46
+ client_pool.release_client = () => {}
47
+ hmail.todo = { rcpt_to: [] }
48
+ hmail.try_deliver = () => {}
49
+ hmail.logerror = () => {}
50
+ })
51
+
52
+ afterEach(() => {
53
+ client_pool.release_client = origRelease
54
+ })
55
+
56
+ it('error then timeout does not throw ERR_UNHANDLED_ERROR', () => {
57
+ const socket = makeSocket()
58
+ hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
59
+ socket.emit('error', new Error('connection refused'))
60
+ assert.doesNotThrow(() => socket.emit('timeout'), 'timeout after error must not crash')
61
+ })
62
+
63
+ it('timeout then error does not throw ERR_UNHANDLED_ERROR', () => {
64
+ const socket = makeSocket()
65
+ hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
66
+ socket.emit('timeout')
67
+ assert.doesNotThrow(
68
+ () => socket.emit('error', new Error('late error')),
69
+ 'error after timeout must not crash',
70
+ )
71
+ })
72
+
73
+ it('multiple timeouts do not throw ERR_UNHANDLED_ERROR', () => {
74
+ const socket = makeSocket()
75
+ hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
76
+ socket.emit('timeout')
77
+ assert.doesNotThrow(() => socket.emit('timeout'), 'second timeout must not crash')
78
+ })
79
+ })
80
+
81
+ it('sort_mx orders by priority ascending', () => {
82
+ const sorted = hmail.sort_mx([
20
83
  { exchange: 'mx2.example.com', priority: 5 },
21
84
  { exchange: 'mx1.example.com', priority: 6 },
22
85
  ])
23
86
  assert.equal(sorted[0].exchange, 'mx2.example.com')
24
- done()
25
87
  })
26
- it('sort_mx, shuffled', (done) => {
27
- const sorted = this.hmail.sort_mx([
88
+
89
+ it('sort_mx shuffles equal-priority entries', () => {
90
+ const sorted = hmail.sort_mx([
28
91
  { exchange: 'mx2.example.com', priority: 5 },
29
92
  { exchange: 'mx1.example.com', priority: 6 },
30
93
  { exchange: 'mx3.example.com', priority: 6 },
31
94
  ])
32
95
  assert.equal(sorted[0].exchange, 'mx2.example.com')
33
- assert.ok(sorted[1].exchange == 'mx3.example.com' || sorted[1].exchange == 'mx1.example.com')
34
- done()
96
+ assert.ok(['mx1.example.com', 'mx3.example.com'].includes(sorted[1].exchange))
35
97
  })
36
- it('force_tls', (done) => {
37
- this.hmail.todo = { domain: 'miss.example.com' }
38
- this.hmail.obtls.cfg = {
39
- force_tls_hosts: ['1.2.3.4', 'hit.example.com'],
40
- }
41
- assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.4' }), true)
42
- assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), false)
43
- this.hmail.todo = { domain: 'hit.example.com' }
44
- assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), true)
45
- done()
98
+
99
+ it('get_force_tls matches by IP and domain', () => {
100
+ hmail.todo = { domain: 'miss.example.com' }
101
+ hmail.obtls.cfg = { force_tls_hosts: ['1.2.3.4', 'hit.example.com'] }
102
+ assert.equal(hmail.get_force_tls({ exchange: '1.2.3.4' }), true)
103
+ assert.equal(hmail.get_force_tls({ exchange: '1.2.3.5' }), false)
104
+ hmail.todo = { domain: 'hit.example.com' }
105
+ assert.equal(hmail.get_force_tls({ exchange: '1.2.3.5' }), true)
46
106
  })
47
107
  })
48
108
 
49
- describe('outbound/hmail.HMailItem', () => {
50
- it('normal queue file', (done) => {
51
- this.hmail = new Hmail(
109
+ const TOOLONG_FIXTURE = 'test/queue/1509000000000_1509000000000_0_99999_ToLong_1_haraka'
110
+
111
+ const makeToolongFixture = () => {
112
+ const buf = Buffer.alloc(50)
113
+ buf.writeUInt32BE(9999, 0) // declares 9999 bytes but file has only 46 after the header
114
+ buf.write('{"domain":"example.com"', 4)
115
+ fs.writeFileSync(TOOLONG_FIXTURE, buf)
116
+ }
117
+
118
+ describe('outbound/hmail.HMailItem — queue file loading', () => {
119
+ before(makeToolongFixture)
120
+
121
+ it('loads a valid queue file', async () => {
122
+ const h = new Hmail(
52
123
  '1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
53
124
  'test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
54
125
  {},
55
126
  )
56
- this.hmail.on('ready', () => {
57
- // console.log(this.hmail);
58
- assert.ok(this.hmail)
59
- done()
60
- })
61
- this.hmail.on('error', (err) => {
62
- console.log(err)
63
- assert.equal(err, undefined)
64
- done()
65
- })
127
+ await onEvent(h, 'ready')
128
+ assert.ok(h)
66
129
  })
67
- it('normal TODO w/multibyte chars loads w/o error', (done) => {
68
- this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_1_qfile', 'test/fixtures/todo_qfile.txt', {})
69
- this.hmail.on('ready', () => {
70
- // console.log(this.hmail);
71
- assert.ok(this.hmail)
72
- done()
73
- })
74
- this.hmail.on('error', (err) => {
75
- console.log(err)
76
- assert.equal(err, undefined)
77
- done()
78
- })
130
+
131
+ it('loads a TODO with multibyte chars without error', async () => {
132
+ const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_1_qfile', 'test/fixtures/todo_qfile.txt', {})
133
+ await onEvent(h, 'ready')
134
+ assert.ok(h)
79
135
  })
80
- it('too short TODO length declared', (done) => {
81
- this.hmail = new Hmail(
136
+
137
+ it('emits error on too-short declared TODO length', async () => {
138
+ const h = new Hmail(
82
139
  '1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
83
140
  'test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
84
141
  {},
85
142
  )
86
- this.hmail.on('ready', () => {
87
- // console.log(this.hmail);
88
- assert.ok(this.hmail)
89
- done()
90
- })
91
- this.hmail.on('error', (err) => {
92
- console.log(err)
93
- assert.ok(err)
94
- done()
143
+ const err = await new Promise((resolve) => {
144
+ h.once('ready', () => resolve(null))
145
+ h.once('error', resolve)
95
146
  })
147
+ assert.ok(err, 'expected an error for truncated TODO')
96
148
  })
97
- it('too long TODO length declared', (done) => {
98
- this.hmail = new Hmail(
99
- '1508269674999_1508269674999_0_34002_socVUF_1_haraka',
100
- 'test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka',
101
- {},
102
- )
103
- this.hmail.on('ready', () => {
104
- // console.log(this.hmail);
105
- assert.ok(this.hmail)
106
- done()
107
- })
108
- this.hmail.on('error', (err) => {
109
- console.log(err)
110
- assert.ok(err)
111
- done()
149
+
150
+ it('emits error on too-long declared TODO length', async () => {
151
+ // Recreate fixture in case a prior run renamed it to the error queue
152
+ makeToolongFixture()
153
+ const h = new Hmail('1509000000000_1509000000000_0_99999_ToLong_1_haraka', TOOLONG_FIXTURE, {})
154
+ const err = await new Promise((resolve) => {
155
+ h.once('ready', () => resolve(null))
156
+ h.once('error', resolve)
112
157
  })
158
+ assert.ok(err, 'expected an error for oversized TODO')
113
159
  })
114
- it('zero-length file load skip w/o crash', (done) => {
115
- this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_zero', 'test/queue/zero-length', {})
116
- this.hmail.on('ready', () => {
117
- assert.ok(this.hmail)
118
- done()
119
- })
120
- this.hmail.on('error', (err) => {
121
- console.error(err)
122
- assert.ok(err)
123
- done()
160
+
161
+ it('skips zero-length file without crash', async () => {
162
+ const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_zero', 'test/queue/zero-length', {})
163
+ await new Promise((resolve) => {
164
+ h.once('ready', resolve)
165
+ h.once('error', resolve)
124
166
  })
167
+ assert.ok(h)
125
168
  })
126
- it('lifecycle, reads and writes a haraka queue file', (done) => {
127
- this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_qfile', 'test/fixtures/todo_qfile.txt', {})
128
169
 
129
- this.hmail.on('error', (err) => {
130
- // console.log(err);
131
- assert.equals(err, undefined)
132
- done()
133
- })
170
+ it('lifecycle: reads and writes a queue file', async () => {
171
+ const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_qfile', 'test/fixtures/todo_qfile.txt', {})
134
172
 
135
- this.hmail.on('ready', () => {
136
- const tmpfile = path.resolve('test', 'test-queue', 'delete-me')
137
- const ws = new fs.createWriteStream(tmpfile)
173
+ await onEvent(h, 'ready')
138
174
 
139
- outbound.build_todo(this.hmail.todo, ws, () => {
140
- // console.log('returned from build_todo, piping')
141
- // console.log(this.hmail.todo)
142
- // assert.equals(this.hmail.todo.message_stream.headers.length, 22);
175
+ const tmpfile = path.resolve('test', 'test-queue', 'delete-me')
176
+ await fs.promises.mkdir(path.dirname(tmpfile), { recursive: true })
177
+ const ws = new fs.WriteStream(tmpfile)
143
178
 
144
- const ds = this.hmail.data_stream()
179
+ await new Promise((resolve, reject) => {
180
+ outbound.build_todo(h.todo, ws, () => {
181
+ const ds = h.data_stream()
145
182
  ds.pipe(ws)
146
-
147
- ws.on('close', () => {
148
- // console.log(this.hmail.todo)
149
- assert.equal(fs.statSync(tmpfile).size, 4204)
150
- done()
151
- })
183
+ ws.on('close', resolve)
184
+ ws.on('error', reject)
152
185
  })
153
186
  })
187
+
188
+ assert.equal(fs.statSync(tmpfile).size, 4204)
189
+ fs.unlinkSync(tmpfile)
154
190
  })
155
191
  })