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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This handcrafted artisanal software is brought to you by:
4
4
 
5
- | <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/Haraka/commits?author=msimerson">1662</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/Haraka/commits?author=baudehlo">969</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/Haraka/commits?author=smfreegard">794</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/959600?v=4"><br><a href="https://github.com/godsflaw">godsflaw</a> (<a href="https://github.com/haraka/Haraka/commits?author=godsflaw">171</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/934254?v=4"><br><a href="https://github.com/analogic">analogic</a> (<a href="https://github.com/haraka/Haraka/commits?author=analogic">42</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/1674289?v=4"><br><a href="https://github.com/Dexus">Dexus</a> (<a href="https://github.com/haraka/Haraka/commits?author=Dexus">42</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/82041?v=4"><br><a href="https://github.com/gramakri">gramakri</a> (<a href="https://github.com/haraka/Haraka/commits?author=gramakri">40</a>)|
5
+ | <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/Haraka/commits?author=msimerson">1666</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/Haraka/commits?author=baudehlo">969</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/Haraka/commits?author=smfreegard">794</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/959600?v=4"><br><a href="https://github.com/godsflaw">godsflaw</a> (<a href="https://github.com/haraka/Haraka/commits?author=godsflaw">171</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/934254?v=4"><br><a href="https://github.com/analogic">analogic</a> (<a href="https://github.com/haraka/Haraka/commits?author=analogic">42</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/1674289?v=4"><br><a href="https://github.com/Dexus">Dexus</a> (<a href="https://github.com/haraka/Haraka/commits?author=Dexus">42</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/82041?v=4"><br><a href="https://github.com/gramakri">gramakri</a> (<a href="https://github.com/haraka/Haraka/commits?author=gramakri">40</a>)|
6
6
  | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
7
7
  | <img height="80" src="https://avatars.githubusercontent.com/u/203240?v=4"><br><a href="https://github.com/lnedry">lnedry</a> (<a href="https://github.com/haraka/Haraka/commits?author=lnedry">27</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/748075?v=4"><br><a href="https://github.com/celesteking">celesteking</a> (<a href="https://github.com/haraka/Haraka/commits?author=celesteking">21</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/791972?v=4"><br><a href="https://github.com/lpatters">lpatters</a> (<a href="https://github.com/haraka/Haraka/commits?author=lpatters">20</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/366268?v=4"><br><a href="https://github.com/chazomaticus">chazomaticus</a> (<a href="https://github.com/haraka/Haraka/commits?author=chazomaticus">19</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/123708?v=4"><br><a href="https://github.com/arlolra">arlolra</a> (<a href="https://github.com/haraka/Haraka/commits?author=arlolra">16</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/271024?v=4"><br><a href="https://github.com/hayesgm">hayesgm</a> (<a href="https://github.com/haraka/Haraka/commits?author=hayesgm">16</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/1573133?v=4"><br><a href="https://github.com/gauravaror">gauravaror</a> (<a href="https://github.com/haraka/Haraka/commits?author=gauravaror">14</a>)|
8
8
  | <img height="80" src="https://avatars.githubusercontent.com/u/260607?v=4"><br><a href="https://github.com/typingArtist">typingArtist</a> (<a href="https://github.com/haraka/Haraka/commits?author=typingArtist">14</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/11343494?v=4"><br><a href="https://github.com/superman20">superman20</a> (<a href="https://github.com/haraka/Haraka/commits?author=superman20">13</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/158380?v=4"><br><a href="https://github.com/darkpixel">darkpixel</a> (<a href="https://github.com/haraka/Haraka/commits?author=darkpixel">12</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/9887966?v=4"><br><a href="https://github.com/KingNoosh">KingNoosh</a> (<a href="https://github.com/haraka/Haraka/commits?author=KingNoosh">11</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/5229495?v=4"><br><a href="https://github.com/tstonis">tstonis</a> (<a href="https://github.com/haraka/Haraka/commits?author=tstonis">10</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/1746394?v=4"><br><a href="https://github.com/wltsmrz">wltsmrz</a> (<a href="https://github.com/haraka/Haraka/commits?author=wltsmrz">9</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/218741413?v=4"><br><a href="https://github.com/SamuelGrave">SamuelGrave</a> (<a href="https://github.com/haraka/Haraka/commits?author=SamuelGrave">9</a>)|
package/Changes.md CHANGED
@@ -4,6 +4,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
4
4
 
5
5
  ### Unreleased
6
6
 
7
+ ### [3.1.5] - 2026-04-02
8
+
9
+ #### Changed
10
+
11
+ - fix(smtp_forward): update AUTH to match WHATWG URL API #3546
12
+ - fix(smtp_forward): queue hook now calls next() after delivery
13
+ — see haraka/message-stream#17
14
+ - deps(all): bump versions to latest
15
+ - test: refactor server, use smtp_client for all tests #3548
16
+ - test runner is now node --test #3547
17
+ - test(smtp_client, tls_socket, smtp_forward): 95% coverage #3546
18
+ - ci: added explicit minimal permissions
19
+
7
20
  ### [3.1.4] - 2026-03-30
8
21
 
9
22
  #### Fixed
@@ -1797,3 +1810,4 @@ config files.
1797
1810
  [3.1.2]: https://github.com/haraka/Haraka/releases/tag/v3.1.2
1798
1811
  [3.1.3]: https://github.com/haraka/Haraka/releases/tag/v3.1.3
1799
1812
  [3.1.4]: https://github.com/haraka/Haraka/releases/tag/v3.1.4
1813
+ [3.1.5]: https://github.com/haraka/Haraka/releases/tag/v3.1.5
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "server",
10
10
  "email"
11
11
  ],
12
- "version": "3.1.4",
12
+ "version": "3.1.5",
13
13
  "homepage": "http://haraka.github.io",
14
14
  "repository": {
15
15
  "type": "git",
@@ -26,13 +26,13 @@
26
26
  "haraka-config": "^1.5.0",
27
27
  "haraka-constants": "^1.0.7",
28
28
  "haraka-dsn": "^1.1.0",
29
- "haraka-email-message": "^1.3.1",
30
- "haraka-message-stream": "^2.0.0",
29
+ "haraka-email-message": "^1.3.2",
30
+ "haraka-message-stream": "^2.0.1",
31
31
  "haraka-net-utils": "^1.7.2",
32
32
  "haraka-notes": "^1.1.1",
33
33
  "haraka-plugin-redis": "^2.0.11",
34
34
  "haraka-results": "^2.3.0",
35
- "haraka-tld": "^1.3.1",
35
+ "haraka-tld": "^1.3.3",
36
36
  "haraka-utils": "^1.1.4",
37
37
  "ipaddr.js": "~2.3.0",
38
38
  "node-gyp": "^12.2.0",
@@ -59,16 +59,16 @@
59
59
  "haraka-plugin-esets": "^1.0.1",
60
60
  "haraka-plugin-fcrdns": "^1.1.2",
61
61
  "haraka-plugin-geoip": "^1.1.2",
62
- "haraka-plugin-greylist": "^1.1.0",
62
+ "haraka-plugin-greylist": "^1.1.1",
63
63
  "haraka-plugin-headers": "^1.1.0",
64
64
  "haraka-plugin-helo.checks": "^1.1.0",
65
- "haraka-plugin-karma": "^2.1.8",
65
+ "haraka-plugin-karma": "^2.2.0",
66
66
  "haraka-plugin-known-senders": "^1.1.3",
67
67
  "haraka-plugin-limit": "^1.2.7",
68
68
  "haraka-plugin-mail_from.is_resolvable": "^1.1.0",
69
69
  "haraka-plugin-messagesniffer": "^1.0.1",
70
70
  "haraka-plugin-p0f": "^1.0.11",
71
- "haraka-plugin-qmail-deliverable": "^1.3.2",
71
+ "haraka-plugin-qmail-deliverable": "^1.3.3",
72
72
  "haraka-plugin-recipient-routes": "^1.3.1",
73
73
  "haraka-plugin-relay": "^1.0.1",
74
74
  "haraka-plugin-rspamd": "^1.4.2",
@@ -82,9 +82,7 @@
82
82
  "devDependencies": {
83
83
  "@haraka/eslint-config": "^2.0.4",
84
84
  "haraka-test-fixtures": "^1.4.1",
85
- "mocha": "^11.7.5",
86
- "mock-require": "^3.0.3",
87
- "nodemailer": "^8.0.4"
85
+ "mock-require": "^3.0.3"
88
86
  },
89
87
  "bugs": {
90
88
  "mail": "haraka.mail@gmail.com",
@@ -247,7 +247,7 @@ exports.queue_forward = function (next, connection) {
247
247
  )
248
248
 
249
249
  function get_rs() {
250
- return connection?.transaction?.results ? connection.transaction.results : connection.results
250
+ return txn?.results ?? connection.results
251
251
  }
252
252
 
253
253
  function dead_sender() {
@@ -320,10 +320,10 @@ exports.get_mx_next_hop = (next_hop) => {
320
320
  exchange: dest.hostname,
321
321
  }
322
322
  if (dest.protocol === 'lmtp:') mx.using_lmtp = true
323
- if (dest.auth) {
323
+ if (dest.username) {
324
324
  mx.auth_type = 'plain'
325
- mx.auth_user = dest.auth.split(':')[0]
326
- mx.auth_pass = dest.auth.split(':')[1]
325
+ mx.auth_user = dest.username
326
+ mx.auth_pass = dest.password
327
327
  }
328
328
  return mx
329
329
  }
package/run_tests CHANGED
@@ -1,23 +1,11 @@
1
1
  #!/bin/sh
2
2
 
3
3
  # Files using node:test
4
- NODE_TEST_FILES="test/transaction.js test/connection.js test/outbound/*.js"
5
-
6
- # Mocha scans test/ but skips transaction.js, connection.js, and outbound/
7
- MOCHA_IGNORE="--ignore=test/transaction.js --ignore=test/connection.js"
8
- MOCHA_TESTS="test test/plugins/auth test/plugins/queue test/plugins"
4
+ NODE_TEST_FILES="test/*.js test/outbound/*.js test/plugins/*.js test/plugins/auth/*.js test/plugins/queue/*.js"
9
5
 
10
6
  if [ -n "$1" ]; then
11
- case "$1" in
12
- test/transaction.js | test/connection.js | test/outbound/*)
13
- node --test "$1"
14
- ;;
15
- *)
16
- npx mocha --exit --timeout=4000 "$1"
17
- ;;
18
- esac
7
+ node --test "$1"
19
8
  else
20
9
  # default, run 'em all
21
- node --test --test-concurrency=1 $NODE_TEST_FILES && \
22
- npx mocha --exit --timeout=4000 $MOCHA_IGNORE $MOCHA_TESTS
10
+ node --test --test-concurrency=1 $NODE_TEST_FILES
23
11
  fi
package/smtp_client.js CHANGED
@@ -216,8 +216,7 @@ class SMTPClient extends events.EventEmitter {
216
216
 
217
217
  release() {
218
218
  if (this.state === STATE.DESTROYED) return
219
- logger.debug(`[smtp_client] ${this.uuid} releasing, state=${this.state}`)
220
- ;[
219
+ const listeners = [
221
220
  'auth',
222
221
  'bad_code',
223
222
  'capabilities',
@@ -233,9 +232,11 @@ class SMTPClient extends events.EventEmitter {
233
232
  'rset',
234
233
  'server_protocol',
235
234
  'xclient',
236
- ].forEach((l) => {
235
+ ]
236
+ logger.debug(`[smtp_client] ${this.uuid} releasing, state=${this.state}`)
237
+ for (const l of listeners) {
237
238
  this.removeAllListeners(l)
238
- })
239
+ }
239
240
 
240
241
  if (this.connected) this.send_command('QUIT')
241
242
  this.destroy()
@@ -349,7 +350,7 @@ exports.get_client_plugin = (plugin, connection, c, callback) => {
349
350
  }
350
351
  }
351
352
 
352
- const hostport = get_hostport(connection, connection.server, c)
353
+ const hostport = get_hostport(connection, c)
353
354
  const smtp_client = new SMTPClient(hostport)
354
355
  logger.info(`[smtp_client] uuid=${smtp_client.uuid} host=${hostport.host} port=${hostport.port} created`)
355
356
 
@@ -468,7 +469,8 @@ exports.get_client_plugin = (plugin, connection, c, callback) => {
468
469
  callback(null, smtp_client)
469
470
  }
470
471
 
471
- function get_hostport(connection, server, cfg) {
472
+ function get_hostport(connection, cfg) {
473
+ const server = connection.server
472
474
  if (cfg.forwarding_host_pool) {
473
475
  if (!server.notes.host_pool) {
474
476
  connection.logwarn(`creating host_pool from ${cfg.forwarding_host_pool}`)
package/test/endpoint.js CHANGED
@@ -1,3 +1,6 @@
1
+ 'use strict'
2
+
3
+ const { describe, it, beforeEach, afterEach } = require('node:test')
1
4
  const assert = require('node:assert')
2
5
 
3
6
  const mock = require('mock-require')
@@ -39,7 +42,7 @@ describe('endpoint', () => {
39
42
  })
40
43
 
41
44
  describe('bind()', () => {
42
- beforeEach((done) => {
45
+ beforeEach(() => {
43
46
  // Mock filesystem and log server + fs method calls
44
47
  const modes = (this.modes = {})
45
48
  const log = (this.log = [])
@@ -63,12 +66,10 @@ describe('endpoint', () => {
63
66
 
64
67
  mock('node:fs/promises', this.mockfs)
65
68
  this.endpoint = mock.reRequire('../endpoint')
66
- done()
67
69
  })
68
70
 
69
- afterEach((done) => {
71
+ afterEach(() => {
70
72
  mock.stop('node:fs/promises')
71
- done()
72
73
  })
73
74
 
74
75
  it('IP socket', async () => {
package/test/host_pool.js CHANGED
@@ -1,20 +1,20 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('node:assert')
3
+ const { describe, it } = require('node:test')
4
+ const assert = require('node:assert/strict')
4
5
 
5
6
  const HostPool = require('../host_pool')
6
7
 
7
8
  describe('HostPool', () => {
8
- it('get a host', (done) => {
9
+ it('get a host', () => {
9
10
  const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
10
11
  const host = pool.get_host()
11
12
 
12
13
  assert.ok(/\d\.\d\.\d\.\d/.test(host.host), `'${host.host}' looks like a IP`)
13
14
  assert.ok(/\d\d\d\d/.test(host.port), `'${host.port}' looks like a port`)
14
- done()
15
15
  })
16
16
 
17
- it('uses all the list', (done) => {
17
+ it('uses all the list', () => {
18
18
  const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
19
19
 
20
20
  const host1 = pool.get_host()
@@ -24,10 +24,9 @@ describe('HostPool', () => {
24
24
  assert.notEqual(host1.host, host2.host)
25
25
  assert.notEqual(host3.host, host2.host)
26
26
  assert.equal(host3.host, host1.host)
27
- done()
28
27
  })
29
28
 
30
- it('default port 25', (done) => {
29
+ it('default port 25', () => {
31
30
  const pool = new HostPool('1.1.1.1, 2.2.2.2')
32
31
 
33
32
  const host1 = pool.get_host()
@@ -35,11 +34,24 @@ describe('HostPool', () => {
35
34
 
36
35
  assert.equal(host1.port, 25, `is port 25: ${host1.port}`)
37
36
  assert.equal(host2.port, 25, `is port 25: ${host2.port}`)
38
- done()
39
37
  })
40
38
 
41
- it('dead host', (done) => {
42
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
39
+ it('dead host', () => {
40
+ const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222', 0.001)
41
+ pool.get_socket = () => ({
42
+ pretendTimeout: () => {},
43
+ setTimeout(ms, cb) {
44
+ this.pretendTimeout = cb
45
+ },
46
+ listeners: {},
47
+ on(ev, cb) {
48
+ this.listeners[ev] = cb
49
+ },
50
+ connect(port, host, cb) {
51
+ cb()
52
+ }, // immediately "connects" to stop retry loop
53
+ destroy() {},
54
+ })
43
55
 
44
56
  pool.failed('1.1.1.1', '1111')
45
57
 
@@ -51,17 +63,30 @@ describe('HostPool', () => {
51
63
  assert.equal(host.host, '2.2.2.2', 'dead host is not returned')
52
64
  host = pool.get_host()
53
65
  assert.equal(host.host, '2.2.2.2', 'dead host is not returned')
54
- done()
55
66
  })
56
67
 
57
68
  // if they're *all* dead, we return a host to try anyway, to keep from
58
69
  // accidentally DOS'ing ourselves if there's a transient but widespread
59
70
  // network outage
60
- it("they're all dead", (done) => {
71
+ it("they're all dead", () => {
61
72
  let host1
62
73
  let host2
63
74
 
64
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
75
+ const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222', 0.001)
76
+ pool.get_socket = () => ({
77
+ pretendTimeout: () => {},
78
+ setTimeout(ms, cb) {
79
+ this.pretendTimeout = cb
80
+ },
81
+ listeners: {},
82
+ on(ev, cb) {
83
+ this.listeners[ev] = cb
84
+ },
85
+ connect(port, host, cb) {
86
+ cb()
87
+ }, // immediately "connects" to stop retry loop
88
+ destroy() {},
89
+ })
65
90
 
66
91
  host1 = pool.get_host()
67
92
 
@@ -79,13 +104,12 @@ describe('HostPool', () => {
79
104
  host2 = pool.get_host()
80
105
  assert.ok(host2, "if they're all dead, try one anyway")
81
106
  assert.notEqual(host1.host, host2.host, 'rotation continues')
82
- done()
83
107
  })
84
108
 
85
109
  // after .01 secs the timer to retry the dead host will fire, and then
86
110
  // we connect using this mock socket, whose "connect" always succeeds
87
111
  // so the code brings the dead host back to life
88
- it('host dead checking timer', (done) => {
112
+ it('host dead checking timer', async () => {
89
113
  let num_reqs = 0
90
114
  const MockSocket = function MockSocket(pool) {
91
115
  // these are the methods called from probe_dead_host
@@ -141,22 +165,24 @@ describe('HostPool', () => {
141
165
 
142
166
  // probe_dead_host() will hit two failures and one success (based on
143
167
  // num_reqs above). So we wait at least 10s for that to happen:
144
- const timer = setTimeout(() => {
145
- clearInterval(interval)
146
- assert.ok(false, 'probe_dead_host failed')
147
- done()
148
- }, 10 * 1000)
149
-
150
- const interval = setInterval(
151
- () => {
152
- if (!pool.dead_hosts['1.1.1.1:1111']) {
153
- clearTimeout(timer)
154
- clearInterval(interval)
155
- assert.ok(true, 'timer un-deaded it')
156
- done()
157
- }
158
- },
159
- retry_secs * 1000 * 3,
160
- )
168
+ await new Promise((resolve, reject) => {
169
+ const timer = setTimeout(() => {
170
+ clearInterval(interval)
171
+ reject(new Error('probe_dead_host failed'))
172
+ }, 10 * 1000)
173
+
174
+ const interval = setInterval(
175
+ () => {
176
+ if (!pool.dead_hosts['1.1.1.1:1111']) {
177
+ clearTimeout(timer)
178
+ clearInterval(interval)
179
+ resolve()
180
+ }
181
+ },
182
+ retry_secs * 1000 * 3,
183
+ )
184
+ })
185
+
186
+ assert.ok(true, 'timer un-deaded it')
161
187
  })
162
188
  })
package/test/logger.js CHANGED
@@ -1,9 +1,11 @@
1
- const assert = require('node:assert')
1
+ 'use strict'
2
+
3
+ const { describe, it, beforeEach } = require('node:test')
4
+ const assert = require('node:assert/strict')
2
5
  const util = require('node:util')
3
6
 
4
- const _set_up = (done) => {
7
+ const _set_up = () => {
5
8
  this.logger = require('../logger')
6
- done()
7
9
  }
8
10
 
9
11
  describe('logger', () => {
@@ -16,55 +18,23 @@ describe('logger', () => {
16
18
  })
17
19
 
18
20
  describe('log', () => {
19
- it('log', () => {
20
- this.logger.deferred_logs = []
21
- assert.equal(0, this.logger.deferred_logs.length)
22
- assert.ok(this.logger.log('WARN', 'test warning'))
23
- assert.equal(1, this.logger.deferred_logs.length)
24
- })
25
-
26
- it('log, w/deferred', () => {
27
- this.logger.plugins = { plugin_list: true }
28
- this.logger.deferred_logs.push({
29
- level: 'INFO',
30
- data: 'log test info',
21
+ const formats = ['DEFAULT', 'LOGFMT', 'JSON']
22
+
23
+ for (const fmt of formats) {
24
+ it(`log in ${fmt} format`, () => {
25
+ this.logger.deferred_logs = []
26
+ this.logger.format = this.logger.formats[fmt]
27
+ assert.ok(this.logger.log('WARN', 'test warning'))
28
+ assert.equal(this.logger.deferred_logs.length, 1)
31
29
  })
32
- assert.ok(this.logger.log('INFO', 'another test info'))
33
- })
34
30
 
35
- it('log in logfmt', () => {
36
- this.logger.deferred_logs = []
37
- this.logger.format = this.logger.formats.LOGFMT
38
- assert.equal(0, this.logger.deferred_logs.length)
39
- assert.ok(this.logger.log('WARN', 'test warning'))
40
- assert.equal(1, this.logger.deferred_logs.length)
41
- })
42
-
43
- it('log in logfmt w/deferred', () => {
44
- this.logger.plugins = { plugin_list: true }
45
- this.logger.deferred_logs.push({
46
- level: 'INFO',
47
- data: 'log test info',
31
+ it(`log in ${fmt} format w/deferred`, () => {
32
+ this.logger.format = this.logger.formats[fmt]
33
+ this.logger.plugins = { plugin_list: true }
34
+ this.logger.deferred_logs.push({ level: 'INFO', data: 'log test info' })
35
+ assert.ok(this.logger.log('INFO', 'another test info'))
48
36
  })
49
- assert.ok(this.logger.log('INFO', 'another test info'))
50
- })
51
-
52
- it('log in json', () => {
53
- this.logger.deferred_logs = []
54
- this.logger.format = this.logger.formats.JSON
55
- assert.equal(0, this.logger.deferred_logs.length)
56
- assert.ok(this.logger.log('WARN', 'test warning'))
57
- assert.equal(1, this.logger.deferred_logs.length)
58
- })
59
-
60
- it('log in json w/deferred', () => {
61
- this.logger.plugins = { plugin_list: true }
62
- this.logger.deferred_logs.push({
63
- level: 'INFO',
64
- data: 'log test info',
65
- })
66
- assert.ok(this.logger.log('INFO', 'another test info'))
67
- })
37
+ }
68
38
  })
69
39
 
70
40
  describe('level', () => {
@@ -75,68 +45,40 @@ describe('logger', () => {
75
45
  })
76
46
 
77
47
  describe('set_format', () => {
78
- it('set format to DEFAULT', () => {
79
- this.logger.format = ''
80
- this.logger.set_format('DEFAULT')
81
- assert.equal(this.logger.format, this.logger.formats.DEFAULT)
82
- })
83
-
84
- it('set format to LOGFMT', () => {
85
- this.logger.format = ''
86
- this.logger.set_format('LOGFMT')
87
- assert.equal(this.logger.format, this.logger.formats.LOGFMT)
88
- })
89
-
90
- it('set format to JSON', () => {
91
- this.logger.format = ''
92
- this.logger.set_format('JSON')
93
- assert.equal(this.logger.format, this.logger.formats.JSON)
94
- })
95
-
96
- it('set format to DEFAULT if empty', () => {
97
- this.logger.format = ''
98
- this.logger.set_format('')
99
- assert.equal(this.logger.format, this.logger.formats.DEFAULT)
100
- })
101
-
102
- it('set format to DEFAULT if lowercase', () => {
103
- this.logger.format = ''
104
- this.logger.set_format('default')
105
- assert.equal(this.logger.format, this.logger.formats.DEFAULT)
106
- })
107
-
108
- it('set format to DEFAULT if invalid', () => {
109
- this.logger.format = ''
110
- this.logger.set_format('invalid')
111
- assert.equal(this.logger.format, this.logger.formats.DEFAULT)
112
- })
48
+ // [input, expected format key]
49
+ const cases = [
50
+ ['DEFAULT', 'DEFAULT'],
51
+ ['LOGFMT', 'LOGFMT'],
52
+ ['JSON', 'JSON'],
53
+ ['', 'DEFAULT'], // empty → DEFAULT
54
+ ['default', 'DEFAULT'], // case-insensitive → DEFAULT
55
+ ['invalid', 'DEFAULT'], // unknown → DEFAULT
56
+ ]
57
+ for (const [input, expectedKey] of cases) {
58
+ it(`set_format(${JSON.stringify(input)}) → ${expectedKey}`, () => {
59
+ this.logger.format = ''
60
+ this.logger.set_format(input)
61
+ assert.equal(this.logger.format, this.logger.formats[expectedKey])
62
+ })
63
+ }
113
64
  })
114
65
 
115
66
  describe('set_loglevel', () => {
116
- it('set loglevel to LOGINFO', () => {
117
- this.logger.set_loglevel('LOGINFO')
118
- assert.equal(this.logger.loglevel, this.logger.levels.LOGINFO)
119
- })
120
-
121
- it('set loglevel to INFO', () => {
122
- this.logger.set_loglevel('INFO')
123
- assert.equal(this.logger.loglevel, this.logger.levels.INFO)
124
- })
125
-
126
- it('set loglevel to EMERG', () => {
127
- this.logger.set_loglevel('emerg')
128
- assert.equal(this.logger.loglevel, this.logger.levels.EMERG)
129
- })
130
-
131
- it('set loglevel to 6', () => {
132
- this.logger.set_loglevel(6)
133
- assert.equal(this.logger.loglevel, 6)
134
- })
135
-
136
- it('set loglevel to WARN if invalid', () => {
137
- this.logger.set_loglevel('invalid')
138
- assert.equal(this.logger.loglevel, this.logger.levels.WARN)
139
- })
67
+ // [input, expected level key or null for numeric assertion]
68
+ const cases = [
69
+ ['LOGINFO', 'LOGINFO'],
70
+ ['INFO', 'INFO'],
71
+ ['emerg', 'EMERG'], // case-insensitive
72
+ [6, null], // numeric passthrough
73
+ ['invalid', 'WARN'], // unknown → WARN
74
+ ]
75
+ for (const [input, expectedKey] of cases) {
76
+ it(`set_loglevel(${JSON.stringify(input)}) → ${expectedKey ?? input}`, () => {
77
+ this.logger.set_loglevel(input)
78
+ const expected = expectedKey ? this.logger.levels[expectedKey] : input
79
+ assert.equal(this.logger.loglevel, expected)
80
+ })
81
+ }
140
82
  })
141
83
 
142
84
  describe('set_timestamps', () => {
@@ -219,40 +161,38 @@ describe('logger', () => {
219
161
  })
220
162
 
221
163
  describe('log_if_level', () => {
222
- it('log_if_level is a function', () => {
223
- assert.ok('function' === typeof this.logger.log_if_level)
164
+ it('is a function', () => {
165
+ assert.equal(typeof this.logger.log_if_level, 'function')
224
166
  })
225
167
 
226
- it('log_if_level test log entry', () => {
168
+ it('returns a logging function', () => {
227
169
  this.logger.loglevel = 9
228
170
  const f = this.logger.log_if_level('INFO', 'LOGINFO')
229
- assert.ok(f)
230
- assert.ok('function' === typeof f)
231
- assert.ok(f('test info message'))
232
- assert.equal(1, this.logger.deferred_logs.length)
233
- // console.log(this.logger.deferred_logs[0]);
234
- assert.equal('INFO', this.logger.deferred_logs[0].level)
235
- })
236
-
237
- it('log_if_level null case', () => {
238
- this.logger.loglevel = 9
239
- const f = this.logger.log_if_level('INFO', 'LOGINFO')
240
- assert.ok(f(null))
241
- assert.equal(2, this.logger.deferred_logs.length)
242
- })
243
-
244
- it('log_if_level false', () => {
245
- this.logger.loglevel = 9
246
- const f = this.logger.log_if_level('INFO', 'LOGINFO')
247
- assert.ok(f(false))
248
- assert.equal(3, this.logger.deferred_logs.length)
249
- })
171
+ assert.equal(typeof f, 'function')
172
+ })
173
+
174
+ // Each of these runs independently with a fresh deferred_logs
175
+ for (const [label, msg] of [
176
+ ['string', 'test info message'],
177
+ ['null', null],
178
+ ['false', false],
179
+ ['0 (falsy number)', 0],
180
+ ]) {
181
+ it(`logs ${label} value and appends to deferred_logs`, () => {
182
+ this.logger.loglevel = 9
183
+ this.logger.deferred_logs = []
184
+ const f = this.logger.log_if_level('INFO', 'LOGINFO')
185
+ assert.ok(f(msg))
186
+ assert.equal(this.logger.deferred_logs.length, 1)
187
+ })
188
+ }
250
189
 
251
- it('log_if_level 0', () => {
190
+ it('records correct level in deferred log entry', () => {
252
191
  this.logger.loglevel = 9
192
+ this.logger.deferred_logs = []
253
193
  const f = this.logger.log_if_level('INFO', 'LOGINFO')
254
- assert.ok(f(0))
255
- assert.equal(4, this.logger.deferred_logs.length)
194
+ f('test info message')
195
+ assert.equal(this.logger.deferred_logs[0].level, 'INFO')
256
196
  })
257
197
  })
258
198