Haraka 3.2.1 → 3.3.1

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 (82) hide show
  1. package/.githooks/pre-commit +41 -0
  2. package/.prettierignore +1 -0
  3. package/.qlty/.gitignore +7 -0
  4. package/.qlty/configs/.shellcheckrc +1 -0
  5. package/.qlty/qlty.toml +15 -0
  6. package/CHANGELOG.md +29 -5
  7. package/CONTRIBUTORS.md +5 -5
  8. package/README.md +6 -3
  9. package/bin/haraka +12 -4
  10. package/config/connection.ini +6 -0
  11. package/connection.js +67 -68
  12. package/contrib/bsd-rc.d/haraka +2 -0
  13. package/docs/CoreConfig.md +2 -0
  14. package/docs/HAProxy.md +4 -1
  15. package/eslint.config.mjs +2 -30
  16. package/haraka.js +2 -2
  17. package/line_socket.js +6 -33
  18. package/outbound/hmail.js +18 -29
  19. package/outbound/index.js +3 -3
  20. package/outbound/queue.js +8 -5
  21. package/package.json +49 -46
  22. package/plugins/auth/auth_proxy.js +7 -4
  23. package/plugins/block_me.js +1 -1
  24. package/plugins/delay_deny.js +1 -1
  25. package/plugins/queue/qmail-queue.js +1 -1
  26. package/plugins/queue/quarantine.js +5 -5
  27. package/plugins/queue/smtp_bridge.js +1 -1
  28. package/plugins/queue/smtp_proxy.js +2 -2
  29. package/plugins/status.js +2 -2
  30. package/plugins/toobusy.js +1 -1
  31. package/plugins.js +4 -3
  32. package/server.js +172 -28
  33. package/smtp_client.js +2 -1
  34. package/test/connection.js +119 -2
  35. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  36. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  37. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  38. package/test/fixtures/line_socket.js +1 -1
  39. package/test/fixtures/util_hmailitem.js +2 -3
  40. package/test/outbound/index.js +6 -7
  41. package/test/outbound/qfile.js +1 -1
  42. package/test/outbound/queue.js +2 -2
  43. package/test/plugins/auth/auth_base.js +17 -17
  44. package/test/plugins/auth/auth_bridge.js +3 -3
  45. package/test/plugins/auth/auth_vpopmaild.js +3 -3
  46. package/test/plugins/auth/flat_file.js +16 -21
  47. package/test/plugins/block_me.js +7 -23
  48. package/test/plugins/data.signatures.js +17 -20
  49. package/test/plugins/delay_deny.js +3 -4
  50. package/test/plugins/prevent_credential_leaks.js +17 -21
  51. package/test/plugins/process_title.js +12 -6
  52. package/test/plugins/queue/deliver.js +7 -8
  53. package/test/plugins/queue/discard.js +3 -4
  54. package/test/plugins/queue/lmtp.js +5 -6
  55. package/test/plugins/queue/qmail-queue.js +7 -8
  56. package/test/plugins/queue/quarantine.js +3 -4
  57. package/test/plugins/queue/smtp_bridge.js +5 -7
  58. package/test/plugins/queue/smtp_forward.js +49 -60
  59. package/test/plugins/queue/smtp_proxy.js +6 -7
  60. package/test/plugins/rcpt_to.host_list_base.js +6 -9
  61. package/test/plugins/rcpt_to.in_host_list.js +6 -11
  62. package/test/plugins/record_envelope_addresses.js +33 -60
  63. package/test/plugins/reseed_rng.js +3 -3
  64. package/test/plugins/status.js +4 -5
  65. package/test/plugins/tarpit.js +3 -4
  66. package/test/plugins/tls.js +3 -5
  67. package/test/plugins/toobusy.js +186 -9
  68. package/test/plugins/xclient.js +7 -4
  69. package/test/server.js +425 -1
  70. package/test/smtp_client.js +11 -18
  71. package/test/tls_socket.js +3 -6
  72. package/tls_socket.js +3 -3
  73. package/transaction.js +3 -3
  74. package/address.js +0 -53
  75. package/endpoint.js +0 -96
  76. package/host_pool.js +0 -169
  77. package/outbound/fsync_writestream.js +0 -44
  78. package/outbound/timer_queue.js +0 -86
  79. package/rfc1869.js +0 -93
  80. package/test/endpoint.js +0 -128
  81. package/test/host_pool.js +0 -188
  82. package/test/rfc1869.js +0 -89
@@ -2,24 +2,24 @@
2
2
  const assert = require('node:assert')
3
3
  const { describe, it, beforeEach } = require('node:test')
4
4
 
5
- const { Address } = require('../../../address')
6
- const fixtures = require('haraka-test-fixtures')
5
+ const { Address } = require('@haraka/email-address')
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
7
  const utils = require('haraka-utils')
8
8
 
9
9
  const _set_up = () => {
10
- this.plugin = new fixtures.plugin('auth/auth_base')
10
+ this.plugin = makePlugin('auth/auth_base', { register: false })
11
11
 
12
12
  this.plugin.get_plain_passwd = (user, cb) => {
13
13
  if (user === 'test') return cb('testpass')
14
14
  return cb(null)
15
15
  }
16
16
 
17
- this.connection = fixtures.connection.createConnection()
17
+ this.connection = makeConnection()
18
18
  this.connection.capabilities = null
19
19
  }
20
20
 
21
21
  const _set_up_2 = () => {
22
- this.plugin = new fixtures.plugin('auth/auth_base')
22
+ this.plugin = makePlugin('auth/auth_base', { register: false })
23
23
 
24
24
  this.plugin.get_plain_passwd = (user, connection, cb) => {
25
25
  connection.notes.auth_custom_note = 'custom_note'
@@ -27,12 +27,12 @@ const _set_up_2 = () => {
27
27
  return cb(null)
28
28
  }
29
29
 
30
- this.connection = fixtures.connection.createConnection()
30
+ this.connection = makeConnection()
31
31
  this.connection.capabilities = null
32
32
  }
33
33
 
34
34
  const _set_up_custom_pwcb_opts = () => {
35
- this.plugin = new fixtures.plugin('auth/auth_base')
35
+ this.plugin = makePlugin('auth/auth_base', { register: false })
36
36
 
37
37
  this.plugin.check_plain_passwd = (connection, user, passwd, pwok_cb) => {
38
38
  switch (user) {
@@ -53,7 +53,7 @@ const _set_up_custom_pwcb_opts = () => {
53
53
  }
54
54
  }
55
55
 
56
- this.connection = fixtures.connection.createConnection()
56
+ this.connection = makeConnection()
57
57
  this.connection.capabilities = null
58
58
  this.connection.notes.resp_strings = []
59
59
  this.connection.respond = (code, msg, cb) => {
@@ -266,7 +266,7 @@ describe('auth_base', () => {
266
266
 
267
267
  it('legacyok_nomessage', (t, done) => {
268
268
  this.plugin.check_user(
269
- (code, msg) => {
269
+ (code) => {
270
270
  assert.equal(code, OK)
271
271
  assert.equal(this.connection.relaying, true)
272
272
  assert.deepEqual(this.connection.notes.resp_strings, [[235, '2.7.0 Authentication successful']])
@@ -280,7 +280,7 @@ describe('auth_base', () => {
280
280
 
281
281
  it('legacyfail_nomessage', (t, done) => {
282
282
  this.plugin.check_user(
283
- (code, msg) => {
283
+ (code) => {
284
284
  assert.equal(code, OK)
285
285
  assert.equal(this.connection.relaying, false)
286
286
  assert.deepEqual(this.connection.notes.resp_strings, [[535, '5.7.8 Authentication failed']])
@@ -294,7 +294,7 @@ describe('auth_base', () => {
294
294
 
295
295
  it('legacyok_message', (t, done) => {
296
296
  this.plugin.check_user(
297
- (code, msg) => {
297
+ (code) => {
298
298
  assert.equal(code, OK)
299
299
  assert.equal(this.connection.relaying, true)
300
300
  assert.deepEqual(this.connection.notes.resp_strings, [[235, 'GREAT SUCCESS']])
@@ -308,7 +308,7 @@ describe('auth_base', () => {
308
308
 
309
309
  it('legacyfail_message', (t, done) => {
310
310
  this.plugin.check_user(
311
- (code, msg) => {
311
+ (code) => {
312
312
  assert.equal(code, OK)
313
313
  assert.equal(this.connection.relaying, false)
314
314
  assert.deepEqual(this.connection.notes.resp_strings, [[535, 'FAIL 123']])
@@ -322,7 +322,7 @@ describe('auth_base', () => {
322
322
 
323
323
  it('newok', (t, done) => {
324
324
  this.plugin.check_user(
325
- (code, msg) => {
325
+ (code) => {
326
326
  assert.equal(code, OK)
327
327
  assert.equal(this.connection.relaying, true)
328
328
  assert.deepEqual(this.connection.notes.resp_strings, [[215, 'KOKOKO']])
@@ -336,7 +336,7 @@ describe('auth_base', () => {
336
336
 
337
337
  it('newfail', (t, done) => {
338
338
  this.plugin.check_user(
339
- (code, msg) => {
339
+ (code) => {
340
340
  assert.equal(code, OK)
341
341
  assert.equal(this.connection.relaying, false)
342
342
  assert.deepEqual(this.connection.notes.resp_strings, [[555, 'OHOHOH']])
@@ -355,7 +355,7 @@ describe('auth_base', () => {
355
355
  it('bad auth: no notes should be set', (t, done) => {
356
356
  const credentials = ['matt', 'ttam']
357
357
  this.plugin.check_user(
358
- (code) => {
358
+ () => {
359
359
  assert.equal(this.connection.notes.auth_user, undefined)
360
360
  assert.equal(this.connection.notes.auth_passwd, undefined)
361
361
  done()
@@ -370,7 +370,7 @@ describe('auth_base', () => {
370
370
  const creds = ['test', 'testpass']
371
371
  this.plugin.blankout_password = true
372
372
  this.plugin.check_user(
373
- (code) => {
373
+ () => {
374
374
  assert.equal(this.connection.notes.auth_user, creds[0])
375
375
  assert.equal(this.connection.notes.auth_passwd, undefined)
376
376
  done()
@@ -384,7 +384,7 @@ describe('auth_base', () => {
384
384
  it('good auth: store password (default)', (t, done) => {
385
385
  const creds = ['test', 'testpass']
386
386
  this.plugin.check_user(
387
- (code) => {
387
+ () => {
388
388
  assert.equal(this.connection.notes.auth_user, creds[0])
389
389
  assert.equal(this.connection.notes.auth_passwd, creds[1])
390
390
  done()
@@ -3,13 +3,13 @@
3
3
  const assert = require('node:assert')
4
4
  const { describe, it, beforeEach } = require('node:test')
5
5
 
6
- const fixtures = require('haraka-test-fixtures')
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
7
 
8
8
  describe('auth/auth_bridge', () => {
9
9
  let plugin
10
10
 
11
11
  beforeEach(() => {
12
- plugin = new fixtures.plugin('auth/auth_bridge')
12
+ plugin = makePlugin('auth/auth_bridge', { register: false })
13
13
  plugin.load_flat_ini()
14
14
  })
15
15
 
@@ -28,7 +28,7 @@ describe('auth/auth_bridge', () => {
28
28
  let conn
29
29
 
30
30
  beforeEach(() => {
31
- conn = fixtures.connection.createConnection()
31
+ conn = makeConnection()
32
32
  })
33
33
 
34
34
  it('calls try_auth_proxy with just host when no port configured', (t, done) => {
@@ -4,19 +4,19 @@ const assert = require('node:assert/strict')
4
4
  const path = require('node:path')
5
5
  const { describe, it, beforeEach } = require('node:test')
6
6
 
7
- const fixtures = require('haraka-test-fixtures')
7
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
8
8
 
9
9
  const _set_up = () => {
10
10
  this.backup = {}
11
11
 
12
- this.plugin = new fixtures.plugin('auth/auth_vpopmaild')
12
+ this.plugin = makePlugin('auth/auth_vpopmaild', { register: false })
13
13
  this.plugin.inherits('auth/auth_base')
14
14
 
15
15
  // reset the config/root_path
16
16
  this.plugin.config.root_path = path.resolve(__dirname, '../../../config')
17
17
  this.plugin.cfg = this.plugin.config.get('auth_vpopmaild.ini')
18
18
 
19
- this.connection = fixtures.connection.createConnection()
19
+ this.connection = makeConnection()
20
20
  this.connection.capabilities = null
21
21
  }
22
22
 
@@ -3,22 +3,13 @@
3
3
  const assert = require('node:assert')
4
4
  const { describe, it, beforeEach } = require('node:test')
5
5
 
6
- const fixtures = require('haraka-test-fixtures')
7
-
8
- function makeConnection(opts = {}) {
9
- const conn = fixtures.connection.createConnection()
10
- conn.capabilities = []
11
- conn.notes.allowed_auth_methods = []
12
- conn.remote = { is_private: opts.is_private ?? false }
13
- conn.tls = { enabled: opts.tls_enabled ?? false }
14
- return conn
15
- }
6
+ const { callHook, makeConnection, makePlugin } = require('haraka-test-fixtures')
16
7
 
17
8
  describe('auth/flat_file', () => {
18
9
  let plugin
19
10
 
20
11
  beforeEach(() => {
21
- plugin = new fixtures.plugin('auth/flat_file')
12
+ plugin = makePlugin('auth/flat_file', { register: false })
22
13
  plugin.inherits('auth/auth_base')
23
14
  plugin.load_flat_ini()
24
15
  })
@@ -39,55 +30,59 @@ describe('auth/flat_file', () => {
39
30
 
40
31
  beforeEach(() => {
41
32
  conn = makeConnection()
33
+ conn.capabilities = []
34
+ conn.notes.allowed_auth_methods = []
35
+ conn.remote.is_private = false
36
+ conn.tls.enabled = false
42
37
  })
43
38
 
44
39
  it('skips for public non-TLS connection', (t, done) => {
45
- plugin.hook_capabilities((rc) => {
40
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
46
41
  assert.equal(rc, undefined)
47
42
  assert.equal(conn.capabilities.length, 0)
48
43
  done()
49
- }, conn)
44
+ })
50
45
  })
51
46
 
52
47
  it('adds AUTH methods for private connection (non-TLS)', (t, done) => {
53
48
  conn.remote.is_private = true
54
49
  plugin.cfg.core.methods = 'PLAIN,LOGIN'
55
- plugin.hook_capabilities((rc) => {
50
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
56
51
  assert.equal(rc, undefined)
57
52
  assert.ok(
58
53
  conn.capabilities.some((c) => c.startsWith('AUTH ')),
59
54
  'AUTH capability should be present',
60
55
  )
61
56
  done()
62
- }, conn)
57
+ })
63
58
  })
64
59
 
65
60
  it('adds AUTH methods when TLS is enabled', (t, done) => {
66
61
  conn.tls.enabled = true
67
62
  plugin.cfg.core.methods = 'PLAIN,LOGIN'
68
- plugin.hook_capabilities((rc) => {
63
+ callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
69
64
  assert.equal(rc, undefined)
70
65
  assert.ok(conn.capabilities.some((c) => c.startsWith('AUTH ')))
71
66
  done()
72
- }, conn)
67
+ })
73
68
  })
74
69
 
75
70
  it('sets allowed_auth_methods on connection notes', (t, done) => {
76
71
  conn.tls.enabled = true
77
72
  plugin.cfg.core.methods = 'PLAIN,LOGIN'
78
- plugin.hook_capabilities(() => {
73
+ callHook(plugin, 'hook_capabilities', conn).then(() => {
79
74
  assert.deepEqual(conn.notes.allowed_auth_methods, ['PLAIN', 'LOGIN'])
80
75
  done()
81
- }, conn)
76
+ })
82
77
  })
83
78
 
84
79
  it('does not add AUTH when no methods configured', (t, done) => {
85
80
  conn.tls.enabled = true
86
81
  plugin.cfg.core.methods = null
87
- plugin.hook_capabilities(() => {
82
+ callHook(plugin, 'hook_capabilities', conn).then(() => {
88
83
  assert.equal(conn.capabilities.length, 0)
89
84
  done()
90
- }, conn)
85
+ })
91
86
  })
92
87
  })
93
88
 
@@ -5,8 +5,7 @@ const path = require('node:path')
5
5
  const assert = require('node:assert/strict')
6
6
  const { describe, it, beforeEach, after } = require('node:test')
7
7
 
8
- const fixtures = require('haraka-test-fixtures')
9
- const { Address } = require('@haraka/email-address')
8
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
10
9
  require('haraka-constants').import(global)
11
10
 
12
11
  // block_me appends to <config>/mail_from.blocklist when a sender is blocked;
@@ -15,20 +14,6 @@ after(() => {
15
14
  fs.rmSync(path.resolve('test/config/mail_from.blocklist'), { force: true })
16
15
  })
17
16
 
18
- function makeConnection({
19
- relaying = false,
20
- mailFrom = 'sender@example.com',
21
- rcptTo = ['blocklist@example.com'],
22
- } = {}) {
23
- const conn = fixtures.connection.createConnection()
24
- conn.init_transaction()
25
- conn.relaying = relaying
26
- conn.transaction.mail_from = new Address(`<${mailFrom}>`)
27
- conn.transaction.rcpt_to = rcptTo.map((r) => new Address(`<${r}>`))
28
- conn.transaction.body = { bodytext: '', children: [] }
29
- return conn
30
- }
31
-
32
17
  describe('block_me', () => {
33
18
  let plugin
34
19
 
@@ -36,13 +21,12 @@ describe('block_me', () => {
36
21
  // than the real config dir. block_me also appends matched senders to
37
22
  // mail_from.blocklist; with this override that write lands in test/config too.
38
23
  beforeEach(() => {
39
- plugin = new fixtures.plugin('block_me')
40
- plugin.config = plugin.config.module_config(path.resolve('test'))
24
+ plugin = makePlugin('block_me', { register: false, configDir: 'test' })
41
25
  })
42
26
 
43
27
  describe('hook_data', () => {
44
28
  it('enables body parsing and calls next', (t, done) => {
45
- const conn = makeConnection()
29
+ const conn = makeConnection({ withTxn: true })
46
30
  conn.transaction.parse_body = false
47
31
  plugin.hook_data((rc) => {
48
32
  assert.equal(rc, undefined)
@@ -54,7 +38,7 @@ describe('block_me', () => {
54
38
 
55
39
  describe('hook_data_post', () => {
56
40
  it('calls next when not relaying', (t, done) => {
57
- const conn = makeConnection({ relaying: false })
41
+ const conn = makeConnection({ withTxn: true })
58
42
  plugin.hook_data_post((rc) => {
59
43
  assert.equal(rc, undefined)
60
44
  done()
@@ -62,7 +46,7 @@ describe('block_me', () => {
62
46
  })
63
47
 
64
48
  it('calls next when transaction is missing', (t, done) => {
65
- const conn = fixtures.connection.createConnection()
49
+ const conn = makeConnection()
66
50
  conn.relaying = true
67
51
  conn.transaction = null
68
52
  plugin.hook_data_post((rc) => {
@@ -138,7 +122,7 @@ describe('block_me', () => {
138
122
 
139
123
  describe('hook_queue', () => {
140
124
  it('returns OK when block_me note is set on transaction', (t, done) => {
141
- const conn = makeConnection()
125
+ const conn = makeConnection({ withTxn: true })
142
126
  conn.transaction.notes.block_me = 1
143
127
  plugin.hook_queue((rc) => {
144
128
  assert.equal(rc, OK)
@@ -147,7 +131,7 @@ describe('block_me', () => {
147
131
  })
148
132
 
149
133
  it('calls next when block_me note is not set', (t, done) => {
150
- const conn = makeConnection()
134
+ const conn = makeConnection({ withTxn: true })
151
135
  plugin.hook_queue((rc) => {
152
136
  assert.equal(rc, undefined)
153
137
  done()
@@ -3,26 +3,19 @@
3
3
  const assert = require('node:assert/strict')
4
4
  const { describe, it, beforeEach } = require('node:test')
5
5
 
6
- const fixtures = require('haraka-test-fixtures')
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
7
  require('haraka-constants').import(global)
8
8
 
9
- function makeConnection(bodytext = '', children = []) {
10
- const conn = fixtures.connection.createConnection()
11
- conn.init_transaction()
12
- conn.transaction.body = { bodytext, children }
13
- return conn
14
- }
15
-
16
9
  describe('data.signatures', () => {
17
10
  let plugin
18
11
 
19
12
  beforeEach(() => {
20
- plugin = new fixtures.plugin('data.signatures')
13
+ plugin = makePlugin('data.signatures', { register: false })
21
14
  })
22
15
 
23
16
  describe('hook_data', () => {
24
17
  it('enables body parsing', (t, done) => {
25
- const conn = makeConnection()
18
+ const conn = makeConnection({ withTxn: true })
26
19
  conn.transaction.parse_body = false
27
20
  plugin.hook_data((rc) => {
28
21
  assert.equal(rc, undefined)
@@ -32,7 +25,7 @@ describe('data.signatures', () => {
32
25
  })
33
26
 
34
27
  it('calls next when there is no transaction', (t, done) => {
35
- const conn = fixtures.connection.createConnection()
28
+ const conn = makeConnection()
36
29
  conn.transaction = null
37
30
  plugin.hook_data((rc) => {
38
31
  assert.equal(rc, undefined)
@@ -43,7 +36,7 @@ describe('data.signatures', () => {
43
36
 
44
37
  describe('hook_data_post', () => {
45
38
  it('calls next when there is no transaction', (t, done) => {
46
- const conn = fixtures.connection.createConnection()
39
+ const conn = makeConnection()
47
40
  conn.transaction = null
48
41
  plugin.hook_data_post((rc) => {
49
42
  assert.equal(rc, undefined)
@@ -53,7 +46,8 @@ describe('data.signatures', () => {
53
46
 
54
47
  it('calls next when signature list is empty', (t, done) => {
55
48
  plugin.config.get = (name, type) => (type === 'list' ? [] : {})
56
- const conn = makeConnection('This is some email body text')
49
+ const conn = makeConnection({ withTxn: true })
50
+ conn.transaction.body = { bodytext: 'This is some email body text', children: [] }
57
51
  plugin.hook_data_post((rc) => {
58
52
  assert.equal(rc, undefined)
59
53
  done()
@@ -62,7 +56,8 @@ describe('data.signatures', () => {
62
56
 
63
57
  it('denies when body matches a signature', (t, done) => {
64
58
  plugin.config.get = (name, type) => (type === 'list' ? ['spam_signature_text'] : {})
65
- const conn = makeConnection('Buy cheap meds! spam_signature_text here')
59
+ const conn = makeConnection({ withTxn: true })
60
+ conn.transaction.body = { bodytext: 'Buy cheap meds! spam_signature_text here', children: [] }
66
61
  plugin.hook_data_post((rc, msg) => {
67
62
  assert.equal(rc, DENY)
68
63
  assert.ok(msg.includes('spam'))
@@ -72,7 +67,8 @@ describe('data.signatures', () => {
72
67
 
73
68
  it('calls next when body does not match any signature', (t, done) => {
74
69
  plugin.config.get = (name, type) => (type === 'list' ? ['bad_pattern'] : {})
75
- const conn = makeConnection('Totally normal email body')
70
+ const conn = makeConnection({ withTxn: true })
71
+ conn.transaction.body = { bodytext: 'Totally normal email body', children: [] }
76
72
  plugin.hook_data_post((rc) => {
77
73
  assert.equal(rc, undefined)
78
74
  done()
@@ -81,13 +77,12 @@ describe('data.signatures', () => {
81
77
 
82
78
  it('denies when a child body part matches a signature', (t, done) => {
83
79
  plugin.config.get = (name, type) => (type === 'list' ? ['spam_in_child'] : {})
84
- const conn = fixtures.connection.createConnection()
85
- conn.init_transaction()
80
+ const conn = makeConnection({ withTxn: true })
86
81
  conn.transaction.body = {
87
82
  bodytext: 'clean parent text',
88
83
  children: [{ bodytext: 'spam_in_child content here', children: [] }],
89
84
  }
90
- plugin.hook_data_post((rc, msg) => {
85
+ plugin.hook_data_post((rc) => {
91
86
  assert.equal(rc, DENY)
92
87
  done()
93
88
  }, conn)
@@ -95,7 +90,8 @@ describe('data.signatures', () => {
95
90
 
96
91
  it('calls next when multiple signatures do not match', (t, done) => {
97
92
  plugin.config.get = (name, type) => (type === 'list' ? ['sig_one', 'sig_two', 'sig_three'] : {})
98
- const conn = makeConnection('No matching signatures here at all')
93
+ const conn = makeConnection({ withTxn: true })
94
+ conn.transaction.body = { bodytext: 'No matching signatures here at all', children: [] }
99
95
  plugin.hook_data_post((rc) => {
100
96
  assert.equal(rc, undefined)
101
97
  done()
@@ -104,7 +100,8 @@ describe('data.signatures', () => {
104
100
 
105
101
  it('matches the first of multiple signatures', (t, done) => {
106
102
  plugin.config.get = (name, type) => (type === 'list' ? ['no_match', 'buy_cheap_pills'] : {})
107
- const conn = makeConnection('This message has buy_cheap_pills for you')
103
+ const conn = makeConnection({ withTxn: true })
104
+ conn.transaction.body = { bodytext: 'This message has buy_cheap_pills for you', children: [] }
108
105
  plugin.hook_data_post((rc) => {
109
106
  assert.equal(rc, DENY)
110
107
  done()
@@ -3,7 +3,7 @@
3
3
  const assert = require('node:assert/strict')
4
4
  const { describe, it, beforeEach } = require('node:test')
5
5
 
6
- const fixtures = require('haraka-test-fixtures')
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
7
  require('haraka-constants').import(global)
8
8
 
9
9
  // params layout: [code, msg, pi_name, pi_function, pi_params, pi_hook]
@@ -15,10 +15,9 @@ describe('delay_deny', () => {
15
15
  let plugin, conn
16
16
 
17
17
  beforeEach(() => {
18
- plugin = new fixtures.plugin('delay_deny')
18
+ plugin = makePlugin('delay_deny', { register: false })
19
19
  plugin.config.get = () => ({ main: {} })
20
- conn = fixtures.connection.createConnection()
21
- conn.init_transaction()
20
+ conn = makeConnection({ withTxn: true })
22
21
  })
23
22
 
24
23
  describe('hook_deny', () => {
@@ -3,12 +3,11 @@
3
3
  const assert = require('node:assert/strict')
4
4
  const { describe, it, beforeEach } = require('node:test')
5
5
 
6
- const fixtures = require('haraka-test-fixtures')
6
+ const { makeConnection, makePlugin } = require('haraka-test-fixtures')
7
7
  require('haraka-constants').import(global)
8
8
 
9
- function makeConnection({ authUser, authPasswd, bodytext = '', children = [] } = {}) {
10
- const conn = fixtures.connection.createConnection()
11
- conn.init_transaction()
9
+ function buildConnection({ authUser, authPasswd, bodytext = '', children = [] } = {}) {
10
+ const conn = makeConnection({ withTxn: true })
12
11
  if (authUser) conn.notes.auth_user = authUser
13
12
  if (authPasswd) conn.notes.auth_passwd = authPasswd
14
13
  conn.transaction.body = { bodytext, children }
@@ -19,12 +18,12 @@ describe('prevent_credential_leaks', () => {
19
18
  let plugin
20
19
 
21
20
  beforeEach(() => {
22
- plugin = new fixtures.plugin('prevent_credential_leaks')
21
+ plugin = makePlugin('prevent_credential_leaks', { register: false })
23
22
  })
24
23
 
25
24
  describe('hook_data', () => {
26
25
  it('does not enable parse_body when no auth credentials', (t, done) => {
27
- const conn = makeConnection()
26
+ const conn = buildConnection()
28
27
  conn.transaction.parse_body = false
29
28
  plugin.hook_data((rc) => {
30
29
  assert.equal(rc, undefined)
@@ -34,7 +33,7 @@ describe('prevent_credential_leaks', () => {
34
33
  })
35
34
 
36
35
  it('enables parse_body when both auth_user and auth_passwd are present', (t, done) => {
37
- const conn = makeConnection({ authUser: 'user@example.com', authPasswd: 'secret' })
36
+ const conn = buildConnection({ authUser: 'user@example.com', authPasswd: 'secret' })
38
37
  conn.transaction.parse_body = false
39
38
  plugin.hook_data((rc) => {
40
39
  assert.equal(rc, undefined)
@@ -44,7 +43,7 @@ describe('prevent_credential_leaks', () => {
44
43
  })
45
44
 
46
45
  it('does not enable parse_body when only auth_user is set', (t, done) => {
47
- const conn = makeConnection({ authUser: 'user@example.com' })
46
+ const conn = buildConnection({ authUser: 'user@example.com' })
48
47
  conn.transaction.parse_body = false
49
48
  plugin.hook_data((rc) => {
50
49
  assert.equal(rc, undefined)
@@ -55,8 +54,7 @@ describe('prevent_credential_leaks', () => {
55
54
 
56
55
  it('handles missing connection gracefully', (t, done) => {
57
56
  // Simulate a null-ish connection by calling with empty notes
58
- const conn = fixtures.connection.createConnection()
59
- conn.init_transaction()
57
+ const conn = makeConnection({ withTxn: true })
60
58
  conn.notes = {}
61
59
  plugin.hook_data((rc) => {
62
60
  assert.equal(rc, undefined)
@@ -67,7 +65,7 @@ describe('prevent_credential_leaks', () => {
67
65
 
68
66
  describe('hook_data_post', () => {
69
67
  it('calls next when no auth credentials are set', (t, done) => {
70
- const conn = makeConnection({ bodytext: 'user@example.com secret123' })
68
+ const conn = buildConnection({ bodytext: 'user@example.com secret123' })
71
69
  plugin.hook_data_post((rc) => {
72
70
  assert.equal(rc, undefined)
73
71
  done()
@@ -75,7 +73,7 @@ describe('prevent_credential_leaks', () => {
75
73
  })
76
74
 
77
75
  it('calls next when only auth_user is set (no password)', (t, done) => {
78
- const conn = makeConnection({ authUser: 'user@example.com', bodytext: 'user@example.com' })
76
+ const conn = buildConnection({ authUser: 'user@example.com', bodytext: 'user@example.com' })
79
77
  plugin.hook_data_post((rc) => {
80
78
  assert.equal(rc, undefined)
81
79
  done()
@@ -83,7 +81,7 @@ describe('prevent_credential_leaks', () => {
83
81
  })
84
82
 
85
83
  it('calls next when body contains neither username nor password', (t, done) => {
86
- const conn = makeConnection({
84
+ const conn = buildConnection({
87
85
  authUser: 'alice@example.com',
88
86
  authPasswd: 'mypassword',
89
87
  bodytext: 'Hello, this is a clean email with no credentials.',
@@ -95,7 +93,7 @@ describe('prevent_credential_leaks', () => {
95
93
  })
96
94
 
97
95
  it('calls next when body contains username but not password', (t, done) => {
98
- const conn = makeConnection({
96
+ const conn = buildConnection({
99
97
  authUser: 'alice@example.com',
100
98
  authPasswd: 'mypassword',
101
99
  bodytext: 'Contact alice@example.com for more info.',
@@ -107,7 +105,7 @@ describe('prevent_credential_leaks', () => {
107
105
  })
108
106
 
109
107
  it('denies when body contains both username and password', (t, done) => {
110
- const conn = makeConnection({
108
+ const conn = buildConnection({
111
109
  authUser: 'alice@example.com',
112
110
  authPasswd: 'mypassword',
113
111
  bodytext: 'Please send your login: alice and password: mypassword to activate.',
@@ -120,8 +118,7 @@ describe('prevent_credential_leaks', () => {
120
118
  })
121
119
 
122
120
  it('denies when credentials appear in a child body part', (t, done) => {
123
- const conn = fixtures.connection.createConnection()
124
- conn.init_transaction()
121
+ const conn = makeConnection({ withTxn: true })
125
122
  conn.notes.auth_user = 'bob@example.com'
126
123
  conn.notes.auth_passwd = 's3cr3t'
127
124
  conn.transaction.body = {
@@ -135,7 +132,7 @@ describe('prevent_credential_leaks', () => {
135
132
  })
136
133
 
137
134
  it('handles qualified username (user@domain) by making domain optional', (t, done) => {
138
- const conn = makeConnection({
135
+ const conn = buildConnection({
139
136
  authUser: 'carol@corp.example.com',
140
137
  authPasswd: 'pass123',
141
138
  bodytext: 'carol pass123 credentials',
@@ -149,7 +146,7 @@ describe('prevent_credential_leaks', () => {
149
146
  it('unqualified username (no @) is not split into a partial match', (t, done) => {
150
147
  // Bug: `if (idx)` with idx === -1 treated 'admin' as qualified,
151
148
  // splitting it to user='admi' which then matches 'admiral'.
152
- const conn = makeConnection({
149
+ const conn = buildConnection({
153
150
  authUser: 'admin',
154
151
  authPasswd: 'pw',
155
152
  bodytext: 'the admiral said pw today',
@@ -161,8 +158,7 @@ describe('prevent_credential_leaks', () => {
161
158
  })
162
159
 
163
160
  it('calls next when credentials appear in neither top nor child', (t, done) => {
164
- const conn = fixtures.connection.createConnection()
165
- conn.init_transaction()
161
+ const conn = makeConnection({ withTxn: true })
166
162
  conn.notes.auth_user = 'dave@example.com'
167
163
  conn.notes.auth_passwd = 'xyzzy'
168
164
  conn.transaction.body = {