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
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">
|
|
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.
|
|
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.
|
|
30
|
-
"haraka-message-stream": "^2.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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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.
|
|
323
|
+
if (dest.username) {
|
|
324
324
|
mx.auth_type = 'plain'
|
|
325
|
-
mx.auth_user = dest.
|
|
326
|
-
mx.auth_pass = dest.
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
]
|
|
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,
|
|
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,
|
|
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((
|
|
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((
|
|
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
|
|
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', (
|
|
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', (
|
|
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', (
|
|
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', (
|
|
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", (
|
|
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', (
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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 = (
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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('
|
|
223
|
-
assert.
|
|
164
|
+
it('is a function', () => {
|
|
165
|
+
assert.equal(typeof this.logger.log_if_level, 'function')
|
|
224
166
|
})
|
|
225
167
|
|
|
226
|
-
it('
|
|
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.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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('
|
|
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
|
-
|
|
255
|
-
assert.equal(
|
|
194
|
+
f('test info message')
|
|
195
|
+
assert.equal(this.logger.deferred_logs[0].level, 'INFO')
|
|
256
196
|
})
|
|
257
197
|
})
|
|
258
198
|
|