From a9e58a13922af2f1d57c2b901a2162f721b8bcb3 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Sat, 27 Apr 2024 18:11:48 -0700 Subject: [PATCH] tests: switch test runner to mocha (#3336) - test: install deps with --omit=optional (go faster) - tests: lots of cleanups for es6 & brevity --- .github/workflows/ci.yml | 6 +- .github/workflows/coverage.yml | 4 +- Changes.md | 1 + bin/haraka | 2 +- outbound/queue.js | 12 +- outbound/timer_queue.js | 1 + package.json | 32 +- run_tests | 33 +- server.js | 2 +- tests/config/helo.checks.ini | 2 +- tests/connection.js | 564 +++++++-------- tests/endpoint.js | 111 ++- tests/fixtures/util_hmailitem.js | 22 +- tests/fixtures/vm_harness.js | 59 -- tests/host_pool.js | 99 ++- tests/logger.js | 555 +++++++------- tests/outbound/hmail.js | 154 ++-- tests/outbound/index.js | 386 ++++------ tests/outbound/qfile.js | 183 +++-- tests/outbound_bounce_net_errors.js | 120 ++- tests/outbound_bounce_rfc3464.js | 228 +++--- tests/plugins.js | 395 +++++----- tests/plugins/auth/auth_base.js | 873 +++++++++++----------- tests/plugins/auth/auth_vpopmaild.js | 114 ++- tests/plugins/bounce.js | 504 ++++++------- tests/plugins/clamd.js | 392 +++++----- tests/plugins/deprecated/relay_acl.js | 140 ---- tests/plugins/deprecated/relay_all.js | 59 -- tests/plugins/early_talker.js | 155 ++-- tests/plugins/greylist.js | 55 +- tests/plugins/helo.checks.js | 881 +++++++++++------------ tests/plugins/mail_from.is_resolvable.js | 35 +- tests/plugins/queue/smtp_forward.js | 385 +++++----- tests/plugins/rcpt_to.host_list_base.js | 220 +++--- tests/plugins/rcpt_to.in_host_list.js | 363 +++++----- tests/plugins/relay.js | 612 ++++++++-------- tests/plugins/spamassassin.js | 273 ++++--- tests/plugins/status.js | 210 +++--- tests/plugins/tls.js | 122 ++-- tests/rfc1869.js | 104 +-- tests/server.js | 849 +++++++++++----------- tests/smtp_client.js | 329 +++++++-- tests/smtp_client/auth.js | 105 --- tests/smtp_client/basic.js | 101 --- tests/tls_socket.js | 454 ++++++------ tests/transaction.js | 291 ++++---- 46 files changed, 4916 insertions(+), 5681 deletions(-) delete mode 100644 tests/fixtures/vm_harness.js delete mode 100644 tests/plugins/deprecated/relay_acl.js delete mode 100644 tests/plugins/deprecated/relay_all.js delete mode 100644 tests/smtp_client/auth.js delete mode 100644 tests/smtp_client/basic.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7384d2b1c..eaa11f11b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: uses: haraka/.github/.github/workflows/lint.yml@master test: - needs: [ lint, get-lts ] + needs: [ get-lts ] runs-on: ${{ matrix.os }} services: redis: @@ -28,9 +28,7 @@ jobs: name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} with: node-version: ${{ matrix.node-version }} - - name: Install bsdtar - run: sudo apt-get update; sudo apt-get install -y libarchive-tools - - run: npm install + - run: npm install --omit=optional - run: npm run test # TODO: replace the above with this, after plugin/attachment is split diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7d860f962..b8411d1aa 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -9,8 +9,8 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 name: Node.js - name: install libarchive-tools diff --git a/Changes.md b/Changes.md index 7f4ef0d84..f57de18e4 100644 --- a/Changes.md +++ b/Changes.md @@ -47,6 +47,7 @@ - remove last vestiges of header_hide_version (long ago renamed) - server.js: use the local logger methods - get Haraka version from utils.getVersion (which includes git id if running from repo) +- test: convert test runner to mocha #### Fixed diff --git a/bin/haraka b/bin/haraka index 920747489..fb1b8f493 100755 --- a/bin/haraka +++ b/bin/haraka @@ -415,7 +415,7 @@ else if (parsed.test) { port: 1234, }, destroy () {}, - on (event, done) {}, + on (event) {}, end () { process.exit(); }, diff --git a/outbound/queue.js b/outbound/queue.js index 23e00ec21..74173f3ac 100644 --- a/outbound/queue.js +++ b/outbound/queue.js @@ -75,7 +75,6 @@ exports.stat_queue = cb => { exports.load_queue = pid => { // Initialise and load queue // This function is called first when not running under cluster, - // so we create the queue directory if it doesn't already exist. exports.ensure_queue_dir(); exports.delete_dot_files(); @@ -201,7 +200,6 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () { } exports.stats = () => { - return { queue_dir, queue_count, @@ -226,7 +224,7 @@ exports._list_file = (file, cb) => { // we read everything const todo_struct = JSON.parse(todo); todo_struct.rcpt_to = todo_struct.rcpt_to.map(a => new Address (a)); - todo_struct.mail_from = new Address (todo_struct.mail_from); + todo_struct.mail_from = new Address(todo_struct.mail_from); todo_struct.file = file; todo_struct.full_path = path.join(queue_dir, file); const parts = _qfile.parts(file); @@ -247,12 +245,12 @@ exports.flush_queue = (domain, pid) => { if (domain) { exports.list_queue((err, qlist) => { if (err) return logger.error(exports, `Failed to load queue: ${err}`); - qlist.forEach(todo => { + for (const todo of qlist) { if (todo.domain.toLowerCase() != domain.toLowerCase()) return; if (pid && todo.pid != pid) return; // console.log("requeue: ", todo); delivery_queue.push(new HMailItem(todo.file, todo.full_path)); - }); + } }) } else { @@ -266,7 +264,6 @@ exports.load_pid_queue = pid => { } exports.ensure_queue_dir = () => { - // No reason to do this asynchronously // this code is only run at start-up. if (fs.existsSync(queue_dir)) return; @@ -316,8 +313,7 @@ exports._add_hmail = hmail => { exports.scan_queue_pids = cb => { const self = exports; - // Under cluster, this is called first by the master so - // we create the queue directory if it doesn't exist. + // Under cluster, this is called first by the master self.ensure_queue_dir(); self.delete_dot_files(); diff --git a/outbound/timer_queue.js b/outbound/timer_queue.js index d16b46e10..2691089a1 100644 --- a/outbound/timer_queue.js +++ b/outbound/timer_queue.js @@ -21,6 +21,7 @@ class TimerQueue { constructor (interval = 1000) { this.queue = []; this.interval_timer = setInterval(() => { this.fire(); }, interval); + this.interval_timer.unref() // allow server to exit } add (id, ms, cb) { diff --git a/package.json b/package.json index 5809b12d7..04717160b 100644 --- a/package.json +++ b/package.json @@ -24,26 +24,27 @@ "address-rfc2822": "^2.2.1", "async": "^3.2.5", "daemon": "~1.1.0", - "ipaddr.js": "~2.2.0", - "node-gyp": "^10.1.0", - "nopt": "~7.2.0", - "npid": "~0.4.0", - "semver": "~7.6.0", - "sprintf-js": "~1.1.3", - "haraka-config": "^1.1.0", + "haraka-config": "^1.2.4", "haraka-constants": "^1.0.6", "haraka-dsn": "^1.0.5", - "haraka-email-message": "^1.2.2", + "haraka-email-message": "^1.2.3", "haraka-message-stream": "^1.2.1", "haraka-net-utils": "^1.6.0", "haraka-notes": "^1.0.7", "haraka-plugin-redis": "^2.0.7", + "haraka-plugin-spf": "1.2.5", "haraka-results": "^2.2.4", "haraka-tld": "^1.2.1", "haraka-utils": "^1.1.3", + "ipaddr.js": "~2.2.0", + "node-gyp": "^10.1.0", + "nopt": "~7.2.0", + "npid": "~0.4.0", "openssl-wrapper": "^0.3.4", "redis": "~4.6.13", - "sockaddr": "^1.0.1" + "semver": "^7.6.0", + "sockaddr": "^1.0.1", + "sprintf-js": "~1.1.3" }, "optionalDependencies": { "haraka-plugin-access": "^1.1.6", @@ -56,18 +57,17 @@ "haraka-plugin-dns-list": "^1.2.0", "haraka-plugin-elasticsearch": "^8.0.2", "haraka-plugin-fcrdns": "^1.1.0", - "haraka-plugin-graph": "^1.0.5", "haraka-plugin-geoip": "^1.1.0", + "haraka-plugin-graph": "^1.0.5", "haraka-plugin-headers": "^1.0.4", "haraka-plugin-karma": "^2.1.5", + "haraka-plugin-known-senders": "^1.1.0", "haraka-plugin-limit": "^1.2.3", "haraka-plugin-p0f": "^1.0.9", "haraka-plugin-qmail-deliverable": "^1.2.3", - "haraka-plugin-known-senders": "^1.1.0", "haraka-plugin-rcpt-ldap": "^1.1.0", "haraka-plugin-recipient-routes": "^1.2.0", "haraka-plugin-rspamd": "^1.3.1", - "haraka-plugin-spf": "1.2.5", "haraka-plugin-syslog": "^1.0.6", "haraka-plugin-uribl": "^1.0.8", "haraka-plugin-watch": "^2.0.4", @@ -75,10 +75,10 @@ "tmp": "~0.2.3" }, "devDependencies": { - "nodeunit-x": "^0.16.0", + "@haraka/eslint-config": "^1.1.5", "haraka-test-fixtures": "^1.3.7", + "mocha": "^10.4.0", "mock-require": "^3.0.3", - "@haraka/eslint-config": "^1.1.5", "nodemailer": "^6.9.13" }, "bugs": { @@ -90,9 +90,9 @@ "haraka_grep": "./bin/haraka_grep" }, "scripts": { - "test": "node --use_strict run_tests", + "test": "npx mocha --exit --timeout=3000 tests tests/outbound tests/plugins/auth tests/plugins/queue tests/plugins", "lint": "npx eslint@^8 *.js outbound plugins plugins/*/*.js tests tests/*/*.js tests/*/*/*.js bin/haraka", - "lintfix": "npx eslint@^8 --fix *.js outbound plugins plugins/*/*.js tests tests/*/*.js tests/*/*/*.js bin/haraka", + "lint:fix": "npx eslint@^8 --fix *.js outbound plugins plugins/*/*.js tests tests/*/*.js tests/*/*/*.js bin/haraka", "versions": "npx dependency-version-checker check", "versions:fix": "npx dependency-version-checker update && npm run prettier:fix" } diff --git a/run_tests b/run_tests index f3e179463..8bf01d545 100755 --- a/run_tests +++ b/run_tests @@ -1,32 +1,5 @@ -#!/usr/bin/env node +#!/bin/sh -'use strict'; +TESTS=${1:-"tests tests/outbound tests/plugins/auth tests/plugins/queue tests/plugins"} -let reporter; -try { - reporter = require('nodeunit-x').reporters.default; -} -catch (e) { - console.log(`Error: ${e.message} - -Cannot find nodeunit module. Please run the following: - - npm install\n`); - - process.exit(); -} - -process.chdir(__dirname); - -let tests = [ - 'tests', 'tests/outbound', 'tests/plugins', - 'tests/plugins/auth', 'tests/plugins/queue' -]; - -if (process.argv[2]) { - console.log("Running tests: ", process.argv.slice(2)); - tests = process.argv.slice(2); -} -reporter.run(tests, undefined, function (err) { - process.exit(((err) ? 1 : 0)); -}); +npx mocha --exit $TESTS diff --git a/server.js b/server.js index 2a3351dd9..d8d353bea 100644 --- a/server.js +++ b/server.js @@ -220,7 +220,7 @@ Server._graceful = shutdown => { if (shutdown) { Server.loginfo("Workers closed. Shutting down master process subsystems"); for (const module of ['outbound', 'cfreader', 'plugins']) { - process.emit('message', {event: `${module }.shutdown`}); + process.emit('message', {event: `${module}.shutdown`}); } const t2 = setTimeout(shutdown, Server.cfg.main.force_shutdown_timeout * 1000); return t2.unref(); diff --git a/tests/config/helo.checks.ini b/tests/config/helo.checks.ini index 69442163f..3f52f6cd1 100644 --- a/tests/config/helo.checks.ini +++ b/tests/config/helo.checks.ini @@ -14,7 +14,7 @@ forward_dns=true rdns_match=true ; host_mismatch: hostname differs between EHLO invocations host_mismatch=true -proto_mismatch: host sent EHLO but then tries to sent HELO or vice-versa +; proto_mismatch: host sent EHLO but then tries to sent HELO or vice-versa proto_mismatch=true [reject] diff --git a/tests/connection.js b/tests/connection.js index d2037a594..c9564c7ac 100644 --- a/tests/connection.js +++ b/tests/connection.js @@ -1,4 +1,6 @@ +const assert = require('node:assert') + const constants = require('haraka-constants'); const DSN = require('haraka-dsn') @@ -8,7 +10,7 @@ const Server = require('../server'); // huge hack here, but plugin tests need constants constants.import(global); -function _set_up (done) { +const _set_up = (done) => { this.backup = {}; const client = { remotePort: null, @@ -25,306 +27,276 @@ function _set_up (done) { done() } -exports.connectionRaw = { - setUp : _set_up, - 'has remote object' (test) { - test.expect(1); - test.deepEqual(this.connection.remote, { - ip: null, - port: null, - host: null, - info: null, - closed: false, - is_private: false, - is_local: false - }); - test.done(); - }, - 'has local object' (test) { - test.expect(3); - test.equal(this.connection.local.ip, null); - test.equal(this.connection.local.port, null); - test.ok(this.connection.local.host, this.connection.local.host); - test.done(); - }, - 'has tls object' (test) { - test.expect(1); - test.deepEqual(this.connection.tls, { - enabled: false, - advertised: false, - verified: false, - cipher: {}, - }); - test.done(); - }, - 'get_capabilities' (test) { - test.expect(1); - test.deepEqual([], this.connection.get_capabilities()); - test.done(); - }, - 'queue_msg, defined' (test) { - test.expect(1); - test.equal( - 'test message', - this.connection.queue_msg(1, 'test message') - ); - test.done(); - }, - 'queue_msg, default deny' (test) { - test.expect(2); - test.equal( - 'Message denied', - this.connection.queue_msg(DENY) - ); - test.equal( - 'Message denied', - this.connection.queue_msg(DENYDISCONNECT) - ); - test.done(); - }, - 'queue_msg, default denysoft' (test) { - test.expect(2); - test.equal( - 'Message denied temporarily', - this.connection.queue_msg(DENYSOFT) - ); - test.equal( - 'Message denied temporarily', - this.connection.queue_msg(DENYSOFTDISCONNECT) - ); - test.done(); - }, - 'queue_msg, default else' (test) { - test.expect(1); - test.equal('', this.connection.queue_msg('hello')); - test.done(); - }, - 'has normalized connection properties' (test) { - test.expect(5); - this.connection.set('remote', 'ip', '172.16.15.1'); - this.connection.set('hello', 'verb', 'EHLO'); - this.connection.set('tls', 'enabled', true); - - test.equal('172.16.15.1', this.connection.remote.ip); - test.equal(null, this.connection.remote.port); - test.equal('EHLO', this.connection.hello.verb); - test.equal(null, this.connection.hello.host); - test.equal(true, this.connection.tls.enabled); - test.done(); - }, - 'sets remote.is_private and remote.is_local' (test) { - test.expect(2); - test.equal(false, this.connection.remote.is_private); - test.equal(false, this.connection.remote.is_local); - test.done(); - }, - 'has normalized proxy properties, default' (test) { - test.expect(4); - test.equal(false, this.connection.proxy.allowed); - test.equal(null, this.connection.proxy.ip); - test.equal(null, this.connection.proxy.type); - test.equal(null, this.connection.proxy.timer); - test.done(); - }, - 'has normalized proxy properties, set' (test) { - test.expect(4); - this.connection.set('proxy', 'ip', '172.16.15.1'); - this.connection.set('proxy', 'type', 'haproxy'); - this.connection.set('proxy', 'timer', setTimeout(() => {}, 1000)); - this.connection.set('proxy', 'allowed', true); - - test.equal(true, this.connection.proxy.allowed); - test.equal('172.16.15.1', this.connection.proxy.ip); - test.ok(this.connection.proxy.timer); - test.equal(this.connection.proxy.type, 'haproxy'); - test.done(); - }, - /* - 'max_data_exceeded_respond' : function (test) { - test.expect(1); - test.ok(this.connection.max_data_exceeded_respond(DENYSOFT, 'test' )); - test.done(); - } - */ -} +describe('connection', () => { -exports.connectionPrivate = { - setUp (done) { - this.backup = {}; - const client = { - remotePort: 2525, - remoteAddress: '172.16.15.1', - localPort: 25, - localAddress: '172.16.15.254', - destroy: () => { true; }, - }; - const server = { - ip_address: '172.16.15.254', - address () { - return this.ip_address; - } - } - this.connection = connection.createConnection(client, server, Server.cfg); - done(); - }, - 'sets remote.is_private and remote.is_local' (test) { - test.expect(3); - test.equal(true, this.connection.remote.is_private); - test.equal(false, this.connection.remote.is_local); - test.equal(2525, this.connection.remote.port); - test.done(); - }, -} + describe('connectionRaw', () => { + beforeEach(_set_up) + + it('has remote object', () => { + assert.deepEqual(this.connection.remote, { + ip: null, + port: null, + host: null, + info: null, + closed: false, + is_private: false, + is_local: false + }); + }) + + it('has local object', () => { + assert.equal(this.connection.local.ip, null); + assert.equal(this.connection.local.port, null); + assert.ok(this.connection.local.host, this.connection.local.host); + }) + + it('has tls object', () => { + assert.deepEqual(this.connection.tls, { + enabled: false, + advertised: false, + verified: false, + cipher: {}, + }); + }) + + it('get_capabilities', () => { + assert.deepEqual([], this.connection.get_capabilities()); + }) + + it('queue_msg, defined', () => { + assert.equal( + 'test message', + this.connection.queue_msg(1, 'test message') + ); + }) + + it('queue_msg, default deny', () => { + assert.equal( + 'Message denied', + this.connection.queue_msg(DENY) + ); + assert.equal( + 'Message denied', + this.connection.queue_msg(DENYDISCONNECT) + ); + }) + + it('queue_msg, default denysoft', () => { + assert.equal( + 'Message denied temporarily', + this.connection.queue_msg(DENYSOFT) + ); + assert.equal( + 'Message denied temporarily', + this.connection.queue_msg(DENYSOFTDISCONNECT) + ); + }) -exports.connectionLocal = { - setUp (done) { - const client = { - remotePort: 2525, - remoteAddress: '127.0.0.2', - localPort: 25, - localAddress: '172.0.0.1', - destroy: () => { true; }, - }; - const server = { - ip_address: '127.0.0.1', - address () { - return this.ip_address; + it('queue_msg, default else', () => { + assert.equal('', this.connection.queue_msg('hello')); + }) + + it('has normalized connection properties', () => { + this.connection.set('remote', 'ip', '172.16.15.1'); + this.connection.set('hello', 'verb', 'EHLO'); + this.connection.set('tls', 'enabled', true); + + assert.equal('172.16.15.1', this.connection.remote.ip); + assert.equal(null, this.connection.remote.port); + assert.equal('EHLO', this.connection.hello.verb); + assert.equal(null, this.connection.hello.host); + assert.equal(true, this.connection.tls.enabled); + }) + + it('sets remote.is_private and remote.is_local', () => { + assert.equal(false, this.connection.remote.is_private); + assert.equal(false, this.connection.remote.is_local); + }) + + it('has normalized proxy properties, default', () => { + assert.equal(false, this.connection.proxy.allowed); + assert.equal(null, this.connection.proxy.ip); + assert.equal(null, this.connection.proxy.type); + assert.equal(null, this.connection.proxy.timer); + }) + + it('has normalized proxy properties, set', () => { + this.connection.set('proxy', 'ip', '172.16.15.1'); + this.connection.set('proxy', 'type', 'haproxy'); + this.connection.set('proxy', 'timer', setTimeout(() => {}, 1000)); + this.connection.set('proxy', 'allowed', true); + + assert.equal(true, this.connection.proxy.allowed); + assert.equal('172.16.15.1', this.connection.proxy.ip); + assert.ok(this.connection.proxy.timer); + assert.equal(this.connection.proxy.type, 'haproxy'); + }) + }) + + describe('connectionPrivate', () => { + beforeEach((done) => { + this.backup = {}; + const client = { + remotePort: 2525, + remoteAddress: '172.16.15.1', + localPort: 25, + localAddress: '172.16.15.254', + destroy: () => { true; }, + }; + const server = { + ip_address: '172.16.15.254', + address () { + return this.ip_address; + } } - }; - this.connection = connection.createConnection(client, server, Server.cfg); - done(); - }, - 'sets remote.is_private and remote.is_local' (test) { - test.expect(3); - test.equal(true, this.connection.remote.is_private); - test.equal(true, this.connection.remote.is_local); - test.equal(2525, this.connection.remote.port); - test.done(); - }, -} + this.connection = connection.createConnection(client, server, Server.cfg); + done() + }) -exports.get_remote = { - setUp : _set_up, - 'valid hostname' (test) { - test.expect(1); - this.connection.remote.host='a.host.tld' - this.connection.remote.ip='172.16.199.198' - test.equal(this.connection.get_remote('host'), 'a.host.tld [172.16.199.198]'); - test.done(); - }, - 'no hostname' (test) { - test.expect(1); - this.connection.remote.ip='172.16.199.198' - test.equal(this.connection.get_remote('host'), '[172.16.199.198]'); - test.done(); - }, - 'DNSERROR' (test) { - test.expect(1); - this.connection.remote.host='DNSERROR' - this.connection.remote.ip='172.16.199.198' - test.equal(this.connection.get_remote('host'), '[172.16.199.198]'); - test.done(); - }, - 'NXDOMAIN' (test) { - test.expect(1); - this.connection.remote.host='NXDOMAIN' - this.connection.remote.ip='172.16.199.198' - test.equal(this.connection.get_remote('host'), '[172.16.199.198]'); - test.done(); - }, -} + it('sets remote.is_private and remote.is_local', () => { + assert.equal(true, this.connection.remote.is_private); + assert.equal(false, this.connection.remote.is_local); + assert.equal(2525, this.connection.remote.port); + }) + }) -exports.local_info = { - setUp : _set_up, - 'is Haraka/version' (test) { - test.expect(1); - test.ok(/Haraka\/\d.\d/.test(this.connection.local.info), this.connection.local.info); - test.done(); - } -} -exports.relaying = { - setUp : _set_up, - 'sets and gets' (test) { - test.expect(3); - test.equal(this.connection.relaying, false); - test.ok(this.connection.relaying = 'alligators'); - test.equal(this.connection.relaying, 'alligators'); - test.done(); - }, - 'sets and gets in a transaction' (test) { - test.expect(4); - test.equal(this.connection.relaying, false); - this.connection.transaction = {}; - test.ok(this.connection.relaying = 'crocodiles'); - test.equal(this.connection.transaction._relaying, 'crocodiles'); - test.equal(this.connection.relaying, 'crocodiles'); - test.done(); - } -} + describe('connectionLocal', () => { + beforeEach((done) => { + const client = { + remotePort: 2525, + remoteAddress: '127.0.0.2', + localPort: 25, + localAddress: '172.0.0.1', + destroy: () => { true; }, + }; + const server = { + ip_address: '127.0.0.1', + address () { + return this.ip_address; + } + }; + this.connection = connection.createConnection(client, server, Server.cfg); + done(); + }) -exports.get_set = { - setUp : _set_up, - 'sets single level properties' (test) { - test.expect(2); - this.connection.set('encoding', true); - test.ok(this.connection.encoding); - test.ok(this.connection.get('encoding')); - test.done(); - }, - 'sets two level deep properties' (test) { - test.expect(2); - this.connection.set('local.host', 'test'); - test.equal(this.connection.local.host, 'test'); - test.equal(this.connection.get('local.host'), 'test'); - test.done(); - }, - 'sets three level deep properties' (test) { - test.expect(2); - this.connection.set('some.fine.example', true); - test.ok(this.connection.some.fine.example); - test.ok(this.connection.get('some.fine.example')); - test.done(); - }, -} + it('sets remote.is_private and remote.is_local', () => { + assert.equal(true, this.connection.remote.is_private); + assert.equal(true, this.connection.remote.is_local); + assert.equal(2525, this.connection.remote.port); + }) + }) -exports.respond = { - setUp : _set_up, - 'disconnected returns undefined' (test) { - test.expect(2); - this.connection.state = constants.connection.state.DISCONNECTED - test.equal(this.connection.respond(200, 'your lucky day'), undefined); - test.equal(this.connection.respond(550, 'you are jacked'), undefined); - test.done(); - }, - 'state=command, 200' (test) { - test.expect(1); - test.equal(this.connection.respond(200, 'you may pass Go'), '200 you may pass Go\r\n'); - test.done(); - }, - 'DSN 200' (test) { - test.expect(1); - test.equal( - this.connection.respond(200, DSN.create(200, 'you may pass Go')), - '200 2.0.0 you may pass Go\r\n' - ); - test.done(); - }, - 'DSN 550 create' (test) { - test.expect(1); - // note, the DSN code overrides the response code - test.equal( - this.connection.respond(450, DSN.create(550, 'This domain is not in use and does not accept mail')), - '550 5.0.0 This domain is not in use and does not accept mail\r\n' - ); - test.done(); - }, - 'DSN 550 addr_bad_dest_system' (test) { - test.expect(1); - test.equal( - this.connection.respond(550, DSN.addr_bad_dest_system('This domain is not in use and does not accept mail', 550)), - '550 5.1.2 This domain is not in use and does not accept mail\r\n' - ); - test.done(); - }, -} + + describe('get_remote', () => { + beforeEach(_set_up) + + it('valid hostname', () => { + this.connection.remote.host='a.host.tld' + this.connection.remote.ip='172.16.199.198' + assert.equal(this.connection.get_remote('host'), 'a.host.tld [172.16.199.198]'); + }) + + it('no hostname', () => { + this.connection.remote.ip='172.16.199.198' + assert.equal(this.connection.get_remote('host'), '[172.16.199.198]'); + }) + + it('DNSERROR', () => { + this.connection.remote.host='DNSERROR' + this.connection.remote.ip='172.16.199.198' + assert.equal(this.connection.get_remote('host'), '[172.16.199.198]'); + }) + + it('NXDOMAIN', () => { + this.connection.remote.host='NXDOMAIN' + this.connection.remote.ip='172.16.199.198' + assert.equal(this.connection.get_remote('host'), '[172.16.199.198]'); + }) + + }) + + describe('local_info', () => { + beforeEach(_set_up) + + it('is Haraka/version', () => { + assert.ok(/Haraka\/\d.\d/.test(this.connection.local.info), this.connection.local.info); + }) + }) + + describe('relaying', () => { + beforeEach(_set_up) + + it('sets and gets', () => { + assert.equal(this.connection.relaying, false); + assert.ok(this.connection.relaying = 'alligators'); + assert.equal(this.connection.relaying, 'alligators'); + }) + + it('sets and gets in a transaction', () => { + assert.equal(this.connection.relaying, false); + this.connection.transaction = {}; + assert.ok(this.connection.relaying = 'crocodiles'); + assert.equal(this.connection.transaction._relaying, 'crocodiles'); + assert.equal(this.connection.relaying, 'crocodiles'); + }) + }) + + describe('get_set', () => { + beforeEach(_set_up) + + it('sets single level properties', () => { + this.connection.set('encoding', true); + assert.ok(this.connection.encoding); + assert.ok(this.connection.get('encoding')); + }) + + it('sets two level deep properties', () => { + this.connection.set('local.host', 'test'); + assert.equal(this.connection.local.host, 'test'); + assert.equal(this.connection.get('local.host'), 'test'); + }) + + it('sets three level deep properties', () => { + this.connection.set('some.fine.example', true); + assert.ok(this.connection.some.fine.example); + assert.ok(this.connection.get('some.fine.example')); + }) + }) + + describe('respond', () => { + beforeEach(_set_up) + + it('disconnected returns undefined', () => { + this.connection.state = constants.connection.state.DISCONNECTED + assert.equal(this.connection.respond(200, 'your lucky day'), undefined); + assert.equal(this.connection.respond(550, 'you are jacked'), undefined); + }) + + it('state=command, 200', () => { + assert.equal(this.connection.respond(200, 'you may pass Go'), '200 you may pass Go\r\n'); + }) + + it('DSN 200', () => { + assert.equal( + this.connection.respond(200, DSN.create(200, 'you may pass Go')), + '200 2.0.0 you may pass Go\r\n' + ); + }) + + it('DSN 550 create', () => { + // note, the DSN code overrides the response code + assert.equal( + this.connection.respond(450, DSN.create(550, 'This domain is not in use and does not accept mail')), + '550 5.0.0 This domain is not in use and does not accept mail\r\n' + ); + }) + + it('DSN 550 addr_bad_dest_system', () => { + assert.equal( + this.connection.respond(550, DSN.addr_bad_dest_system('This domain is not in use and does not accept mail', 550)), + '550 5.1.2 This domain is not in use and does not accept mail\r\n' + ); + }) + }) +}) \ No newline at end of file diff --git a/tests/endpoint.js b/tests/endpoint.js index 5ccff61c4..0d9404668 100644 --- a/tests/endpoint.js +++ b/tests/endpoint.js @@ -1,43 +1,38 @@ +const assert = require('node:assert') + const mock = require('mock-require'); const endpoint = require('../endpoint'); -module.exports = { - - 'Endpoint toString()' (test) { - test.expect(5); - test.equal( endpoint(25), '[::0]:25' ); - test.equal( endpoint('10.0.0.3', 42), '10.0.0.3:42' ); - test.equal( endpoint('/foo/bar.sock'), '/foo/bar.sock' ); - test.equal( endpoint('/foo/bar.sock:770'), '/foo/bar.sock:770' ); - test.equal( endpoint({address: '::0', port: 80}), '[::0]:80' ); - test.done(); - }, - - 'Endpoint parse': { - 'Number as port' (test) { - test.expect(1); - test.deepEqual( endpoint(25), {host:'::0', port:25} ); - test.done(); - }, - 'Default port if only host' (test) { - test.expect(1); - test.deepEqual( endpoint('10.0.0.3', 42), {host:'10.0.0.3', port:42} ); - test.done(); - }, - 'Unix socket' (test) { - test.expect(1); - test.deepEqual( endpoint('/foo/bar.sock'), {path:'/foo/bar.sock'} ); - test.done(); - }, - 'Unix socket w/mode' (test) { - test.expect(1); - test.deepEqual( endpoint('/foo/bar.sock:770'), {path:'/foo/bar.sock', mode:'770'} ); - test.done(); - }, - }, - - 'Endpoint bind()': { - setUp (done) { +describe('endpoint', () => { + + it('toString()', () => { + assert.equal( endpoint(25), '[::0]:25' ); + assert.equal( endpoint('10.0.0.3', 42), '10.0.0.3:42' ); + assert.equal( endpoint('/foo/bar.sock'), '/foo/bar.sock' ); + assert.equal( endpoint('/foo/bar.sock:770'), '/foo/bar.sock:770' ); + assert.equal( endpoint({address: '::0', port: 80}), '[::0]:80' ); + }) + + describe('parse', () => { + it('Number as port', () => { + assert.deepEqual( endpoint(25), {host:'::0', port:25} ); + }) + + it('Default port if only host', () => { + assert.deepEqual( endpoint('10.0.0.3', 42), {host:'10.0.0.3', port:42} ); + }) + + it('Unix socket', () => { + assert.deepEqual( endpoint('/foo/bar.sock'), {path:'/foo/bar.sock'} ); + }) + + it('Unix socket w/mode', () => { + assert.deepEqual( endpoint('/foo/bar.sock:770'), {path:'/foo/bar.sock', mode:'770'} ); + }) + }) + + describe('bind()', () => { + beforeEach((done) => { // Mock filesystem and log server + fs method calls const modes = this.modes = {} const log = this.log = [] @@ -72,57 +67,49 @@ module.exports = { mock('fs', this.mockfs); this.endpoint = mock.reRequire('../endpoint'); done(); - }, + }) - tearDown (done) { + afterEach((done) => { mock.stop('fs'); done(); - }, + }) - 'IP socket' (test) { - test.expect(1); + it('IP socket', () => { this.endpoint('10.0.0.3:42').bind(this.server, {backlog:19}); - test.deepEqual( + assert.deepEqual( this.log, [ ['listen', {host: '10.0.0.3', port: 42, backlog: 19}], ]); - test.done(); - }, + }) - 'Unix socket' (test) { - test.expect(1); + it('Unix socket', () => { this.endpoint('/foo/bar.sock').bind(this.server, {readableAll:true}); - test.deepEqual( + assert.deepEqual( this.log, [ ['existsSync', '/foo/bar.sock'], ['listen', {path: '/foo/bar.sock', readableAll: true}], ]); - test.done(); - }, + }) - 'Unix socket (pre-existing)' (test) { - test.expect(1); + it('Unix socket (pre-existing)', () => { this.modes['/foo/bar.sock'] = 0o755; this.endpoint('/foo/bar.sock').bind(this.server); - test.deepEqual( + assert.deepEqual( this.log, [ ['existsSync', '/foo/bar.sock'], ['unlinkSync', '/foo/bar.sock'], ['listen', {path: '/foo/bar.sock'}], ]); - test.done(); - }, + }) - 'Unix socket w/mode' (test) { - test.expect(1); + it('Unix socket w/mode', () => { this.endpoint('/foo/bar.sock:764').bind(this.server); - test.deepEqual( + assert.deepEqual( this.log, [ ['existsSync', '/foo/bar.sock'], ['listen', {path: '/foo/bar.sock'}], ['chmodSync', '/foo/bar.sock', 0o764], ]); - test.done(); - }, - }, -} + }) + }) +}) diff --git a/tests/fixtures/util_hmailitem.js b/tests/fixtures/util_hmailitem.js index 5a1302a39..e3237bb3f 100644 --- a/tests/fixtures/util_hmailitem.js +++ b/tests/fixtures/util_hmailitem.js @@ -1,26 +1,26 @@ 'use strict'; -const { Address } = require('address-rfc2821'); -const fixtures = require('haraka-test-fixtures'); +const assert = require('node:assert') +const { Address } = require('address-rfc2821'); +const fixtures = require('haraka-test-fixtures'); /** * Creates a HMailItem instance, which is passed to callback. Reports error on test param if creation fails. * * @param outbound_context - * @param test * @param options * @param callback */ -exports.newMockHMailItem = (outbound_context, test, options, callback) => { +exports.newMockHMailItem = (outbound_context, done, options, callback) => { const opts = options || {}; exports.createHMailItem( outbound_context, opts, (err, hmail) => { if (err) { - test.ok(false, `Could not create HMailItem: ${err}`); - test.done(); + assert.ok(false, `Could not create HMailItem: ${err}`); + done() return; } if (!hmail.todo) { @@ -102,7 +102,7 @@ exports.createHMailItem = (outbound_context, options, callback) => { * @param test * @param playbook */ -exports.playTestSmtpConversation = (hmail, socket, test, playbook, callback) => { +exports.playTestSmtpConversation = (hmail, socket, done, playbook, callback) => { const testmx = { bind_helo: "haraka.test", exchange: "remote.testhost", @@ -112,17 +112,17 @@ exports.playTestSmtpConversation = (hmail, socket, test, playbook, callback) => socket.write = line => { //console.log('MockSocket.write(' + line.replace(/\n/, '\\n').replace(/\r/, '\\r') + ')'); if (playbook.length == 0) { - test.ok(false, 'missing next playbook entry'); - test.done(); + assert.ok(false, 'missing next playbook entry'); + done() return; } let expected; while (false != (expected = getNextEntryFromPlaybook('haraka', playbook))) { if (typeof expected.test === 'function') { - test.ok(expected.test(line), expected.description || `Expected that line works with func: ${expected.test}`); + assert.ok(expected.test(line), expected.description || `Expected that line works with func: ${expected.test}`); } else { - test.equals(`${expected.test}\r\n`, line, expected.description || `Expected that line equals: ${expected.test}`); + assert.equal(`${expected.test}\r\n`, line, expected.description || `Expected that line equals: ${expected.test}`); } if (expected.end_test === true) { setTimeout(() => { diff --git a/tests/fixtures/vm_harness.js b/tests/fixtures/vm_harness.js deleted file mode 100644 index 7104fe632..000000000 --- a/tests/fixtures/vm_harness.js +++ /dev/null @@ -1,59 +0,0 @@ - -const fs = require('fs'); -const path = require('path'); -const vm = require('vm'); - -function dot_files (element) { - return element.match(/^\./) == null; -} - -exports.sandbox_require = id => { - if (id[0] == '.' && id[1] != '.') { - let override; - try { - override = path.join(__dirname, `${id}.js`); - fs.statSync(override); - id = override; - } - catch (e) { - try { - override = path.join(__dirname, '..', '..', 'outbound', `${id.replace(/^[./]*/, '')}.js`); - fs.statSync(override); - id = override; - } - catch (err) { - id = `../../${ id.replace(/^[./]*/, '')}`; - } - } - } - else if (id[0] == '.' && id[1] == '.') { - id = `../../${ id.replace(/^[./]*/, '')}`; - } - return require(id); -} - -function make_test (module_path, test_path, additional_sandbox) { - return test => { - let code = fs.readFileSync(module_path); - code += fs.readFileSync(test_path); - const sandbox = { - require: exports.sandbox_require, - console, - Buffer, - exports: {}, - test - }; - for (const k of Object.keys(additional_sandbox)) { - sandbox[k] = additional_sandbox[k]; - } - vm.runInNewContext(code, sandbox); - }; -} - -exports.add_tests = (module_path, tests_path, test_exports, add_to_sandbox) => { - const additional_sandbox = add_to_sandbox || {}; - const tests = fs.readdirSync(tests_path).filter(dot_files); - for (const test of tests) { - test_exports[test] = make_test(module_path, tests_path + test, additional_sandbox); - } -} diff --git a/tests/host_pool.js b/tests/host_pool.js index b0ef6dfda..946e8f342 100644 --- a/tests/host_pool.js +++ b/tests/host_pool.js @@ -1,24 +1,21 @@ "use strict"; +const assert = require('node:assert') + const HostPool = require('../host_pool'); -exports.HostPool = { - "get a host": test => { - test.expect(2); +describe('HostPool', () => { + it("get a host", (done) => { const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222'); - const host = pool.get_host(); - test.ok( /\d\.\d\.\d\.\d/.test(host.host), - `'${host.host}' looks like a IP`); - test.ok( /\d\d\d\d/.test(host.port), - `'${host.port}' looks like a port`); + assert.ok( /\d\.\d\.\d\.\d/.test(host.host), `'${host.host}' looks like a IP`); + assert.ok( /\d\d\d\d/.test(host.port), `'${host.port}' looks like a port`); + done() + }) - test.done(); - }, - "uses all the list": test => { - test.expect(3); + it("uses all the list", (done) => { const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222'); @@ -26,29 +23,24 @@ exports.HostPool = { const host2 = pool.get_host(); const host3 = pool.get_host(); - test.notEqual(host1.host, host2.host); - test.notEqual(host3.host, host2.host); - test.equal(host3.host, host1.host); - - test.done(); - }, - "default port 25": test => { - test.expect(2); + assert.notEqual(host1.host, host2.host); + assert.notEqual(host3.host, host2.host); + assert.equal(host3.host, host1.host); + done() + }) + it("default port 25", (done) => { const pool = new HostPool('1.1.1.1, 2.2.2.2'); const host1 = pool.get_host(); const host2 = pool.get_host(); - test.equal(host1.port, 25, `is port 25: ${host1.port}`); - test.equal(host2.port, 25, `is port 25: ${host2.port}`); - - test.done(); - }, - - "dead host": test => { - test.expect(3); + assert.equal(host1.port, 25, `is port 25: ${host1.port}`); + assert.equal(host2.port, 25, `is port 25: ${host2.port}`); + done() + }) + it("dead host", (done) => { const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222'); pool.failed('1.1.1.1', '1111'); @@ -56,20 +48,18 @@ exports.HostPool = { let host; host = pool.get_host(); - test.equal(host.host, '2.2.2.2', 'dead host is not returned'); + assert.equal(host.host, '2.2.2.2', 'dead host is not returned'); host = pool.get_host(); - test.equal(host.host, '2.2.2.2', 'dead host is not returned'); + assert.equal(host.host, '2.2.2.2', 'dead host is not returned'); host = pool.get_host(); - test.equal(host.host, '2.2.2.2', 'dead host is not returned'); - - test.done(); - }, + assert.equal(host.host, '2.2.2.2', 'dead host is not returned'); + done() + }) // if they're *all* dead, we return a host to try anyway, to keep from // accidentally DOS'ing ourselves if there's a transient but widespread // network outage - "they're all dead": test => { - test.expect(6); + it("they're all dead", (done) => { let host1; let host2; @@ -82,26 +72,23 @@ exports.HostPool = { pool.failed('2.2.2.2', '2222'); host2 = pool.get_host(); - test.ok (host2, "if they're all dead, try one anyway"); - test.notEqual(host1.host, host2.host, "rotation continues"); + assert.ok (host2, "if they're all dead, try one anyway"); + assert.notEqual(host1.host, host2.host, "rotation continues"); host1 = pool.get_host(); - test.ok (host1, "if they're all dead, try one anyway"); - test.notEqual(host1.host, host2.host, "rotation continues"); + assert.ok (host1, "if they're all dead, try one anyway"); + assert.notEqual(host1.host, host2.host, "rotation continues"); host2 = pool.get_host(); - test.ok (host2, "if they're all dead, try one anyway"); - test.notEqual(host1.host, host2.host, "rotation continues"); - - test.done(); - }, - + assert.ok (host2, "if they're all dead, try one anyway"); + assert.notEqual(host1.host, host2.host, "rotation continues"); + done() + }) // after .01 secs the timer to retry the dead host will fire, and then // we connect using this mock socket, whose "connect" always succeeds // so the code brings the dead host back to life - "host dead checking timer": test => { - test.expect(2); + it("host dead checking timer", (done) => { let num_reqs = 0; const MockSocket = function MockSocket (pool) { @@ -144,7 +131,6 @@ exports.HostPool = { } }; this.destroy = () => {}; - }; const retry_secs = 0.001; // 1ms @@ -156,24 +142,23 @@ exports.HostPool = { // mark the host as failed and start up the retry timers pool.failed('1.1.1.1', '1111'); - test.ok(pool.dead_hosts["1.1.1.1:1111"], 'yes it was marked dead'); + assert.ok(pool.dead_hosts["1.1.1.1:1111"], 'yes it was marked dead'); // probe_dead_host() will hit two failures and one success (based on // num_reqs above). So we wait at least 10s for that to happen: const timer = setTimeout(() => { clearInterval(interval); - test.ok(false, 'probe_dead_host failed'); - test.done(); + assert.ok(false, 'probe_dead_host failed'); + done() }, 10 * 1000); const interval = setInterval(() => { if (!pool.dead_hosts["1.1.1.1:1111"]) { clearTimeout(timer); clearInterval(interval); - test.ok(true, 'timer un-deaded it'); - test.done(); + assert.ok(true, 'timer un-deaded it'); + done() } }, retry_secs * 1000 * 3 ); - - } -} + }) +}) diff --git a/tests/logger.js b/tests/logger.js index 8d152295d..359ab63cb 100644 --- a/tests/logger.js +++ b/tests/logger.js @@ -1,327 +1,258 @@ +const assert = require('node:assert') +const util = require('node:util'); -const util = require('util'); - -function _set_up (callback) { +const _set_up = (done) => { this.logger = require('../logger'); - callback(); -} -function _tear_down (callback) { - callback(); + done(); } -exports.init = { - setUp : _set_up, - tearDown : _tear_down, - 'logger' (test) { - test.expect(1); - test.ok(this.logger); - test.done(); - }, -} +describe('logger', () => { + beforeEach(_set_up) -exports.log = { - setUp : _set_up, - tearDown : _tear_down, - 'log' (test) { - this.logger.deferred_logs = []; - test.expect(3); - test.equal(0, this.logger.deferred_logs.length); - test.ok(this.logger.log('WARN','test warning')); - test.equal(1, this.logger.deferred_logs.length); - test.done(); - }, - 'log, w/deferred' (test) { - test.expect(1); - this.logger.plugins = { plugin_list: true }; - this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); - test.ok(this.logger.log('INFO', 'another test info')); - test.done(); - }, - 'log in logfmt' (test) { - this.logger.deferred_logs = []; - test.expect(3); - this.logger.format = this.logger.formats.LOGFMT; - test.equal(0, this.logger.deferred_logs.length); - test.ok(this.logger.log('WARN','test warning')); - test.equal(1, this.logger.deferred_logs.length); - test.done(); - }, - 'log in logfmt w/deferred' (test) { - test.expect(1); - this.logger.plugins = { plugin_list: true }; - this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); - test.ok(this.logger.log('INFO', 'another test info')); - test.done(); - }, - 'log in json' (test) { - this.logger.deferred_logs = []; - test.expect(3); - this.logger.format = this.logger.formats.JSON; - test.equal(0, this.logger.deferred_logs.length); - test.ok(this.logger.log('WARN','test warning')); - test.equal(1, this.logger.deferred_logs.length); - test.done(); - }, - 'log in json w/deferred' (test) { - test.expect(1); - this.logger.plugins = { plugin_list: true }; - this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); - test.ok(this.logger.log('INFO', 'another test info')); - test.done(); - }, -} + describe('init', () => { + it('logger', () => { + assert.ok(this.logger); + }) + }) -exports.level = { - setUp : _set_up, - tearDown : _tear_down, - 'both INFO and LOGINFO are log levels' (test) { - test.expect(2); - test.equal(this.logger.levels.INFO, 6); - test.equal(this.logger.levels.LOGINFO, 6); - test.done(); - }, -} + describe('log', () => { + it('log', () => { + this.logger.deferred_logs = []; + assert.equal(0, this.logger.deferred_logs.length); + assert.ok(this.logger.log('WARN','test warning')); + assert.equal(1, this.logger.deferred_logs.length); + }) -exports.set_format = { - setUp : _set_up, - tearDown : _tear_down, - 'set format to DEFAULT' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format('DEFAULT'); - test.equal(this.logger.format, this.logger.formats.DEFAULT); - test.done(); - }, - 'set format to LOGFMT' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format('LOGFMT'); - test.equal(this.logger.format, this.logger.formats.LOGFMT); - test.done(); - }, - 'set format to JSON' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format('JSON'); - test.equal(this.logger.format, this.logger.formats.JSON); - test.done(); - }, - 'set format to DEFAULT if empty' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format(''); - test.equal(this.logger.format, this.logger.formats.DEFAULT); - test.done(); - }, - 'set format to DEFAULT if lowercase' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format('default'); - test.equal(this.logger.format, this.logger.formats.DEFAULT); - test.done(); - }, - 'set format to DEFAULT if invalid' (test) { - test.expect(1); - this.logger.format = ''; - this.logger.set_format('invalid'); - test.equal(this.logger.format, this.logger.formats.DEFAULT); - test.done(); - }, -} + it('log, w/deferred', () => { + this.logger.plugins = { plugin_list: true }; + this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); + assert.ok(this.logger.log('INFO', 'another test info')); + }) -exports.set_loglevel = { - setUp : _set_up, - tearDown : _tear_down, - 'set loglevel to LOGINFO' (test) { - test.expect(1); - this.logger.set_loglevel('LOGINFO'); - test.equal(this.logger.loglevel, this.logger.levels.LOGINFO); - test.done(); - }, - 'set loglevel to INFO' (test) { - test.expect(1); - this.logger.set_loglevel('INFO'); - test.equal(this.logger.loglevel, this.logger.levels.INFO); - test.done(); - }, - 'set loglevel to EMERG' (test) { - test.expect(1); - this.logger.set_loglevel('emerg'); - test.equal(this.logger.loglevel, this.logger.levels.EMERG); - test.done(); - }, - 'set loglevel to 6' (test) { - test.expect(1); - this.logger.set_loglevel(6); - test.equal(this.logger.loglevel, 6); - test.done(); - }, - 'set loglevel to WARN if invalid' (test) { - test.expect(1); - this.logger.set_loglevel('invalid'); - test.equal(this.logger.loglevel, this.logger.levels.WARN); - test.done(); - }, -} + it('log in logfmt', () => { + this.logger.deferred_logs = []; + this.logger.format = this.logger.formats.LOGFMT; + assert.equal(0, this.logger.deferred_logs.length); + assert.ok(this.logger.log('WARN','test warning')); + assert.equal(1, this.logger.deferred_logs.length); + }) -exports.set_timestamps = { - setUp : _set_up, - tearDown : _tear_down, - 'set timestamps to false' (test) { - test.expect(1); - this.logger.timestamps = undefined; - this.logger.set_timestamps(false); - test.equal(this.logger.timestamps, false); - test.done(); - }, - 'set timestamps to true' (test) { - test.expect(1); - this.logger.timestamps = undefined; - this.logger.set_timestamps(true); - test.equal(this.logger.timestamps, true); - test.done(); - }, -} + it('log in logfmt w/deferred', () => { + this.logger.plugins = { plugin_list: true }; + this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); + assert.ok(this.logger.log('INFO', 'another test info')); + }) -exports.would_log = { - setUp : _set_up, - tearDown : _tear_down, - 'should' (test) { - test.expect(3); - this.logger.loglevel = 4; - test.equal(false, this.logger.would_log(7)); - test.equal(false, this.logger.would_log(6)); - test.equal(false, this.logger.would_log(5)); - test.done(); - }, - 'should not' (test) { - test.expect(4); - this.logger.loglevel = 4; - test.equal(true, this.logger.would_log(4)); - test.equal(true, this.logger.would_log(3)); - test.equal(true, this.logger.would_log(2)); - test.equal(true, this.logger.would_log(1)); - test.done(); - }, -} + it('log in json', () => { + this.logger.deferred_logs = []; + this.logger.format = this.logger.formats.JSON; + assert.equal(0, this.logger.deferred_logs.length); + assert.ok(this.logger.log('WARN','test warning')); + assert.equal(1, this.logger.deferred_logs.length); + }) -exports.log_respond = { - setUp : _set_up, - tearDown : _tear_down, - 'invalid retval' (test) { - test.expect(1); - test.equal(false, this.logger.log_respond(901)); - test.done(); - }, - 'valid retval' (test) { - test.expect(1); - const data = { level: 'INFO', data: "test data" }; - test.equal(true, this.logger.log_respond(900, 'test msg', data)); - test.done(); - }, -} + it('log in json w/deferred', () => { + this.logger.plugins = { plugin_list: true }; + this.logger.deferred_logs.push( { level: 'INFO', data: 'log test info'} ); + assert.ok(this.logger.log('INFO', 'another test info')); + }) + }) -exports.dump_logs = { - setUp : _set_up, - tearDown : _tear_down, - 'empty' (test) { - test.expect(1); - test.ok(this.logger.dump_logs(0)); - test.done(); - }, - 'with deferred' (test) { - test.expect(2); - this.logger.deferred_logs.push( { level: 'info', data: 'test info'} ); - this.logger.deferred_logs.push( { level: 'INFO', data: 'test info, color'} ); - this.logger.deferred_logs.push( { level: 'WARN', data: 'test warn, color'} ); - test.ok(this.logger.dump_logs(0)); - test.ok(this.logger.deferred_logs.length === 0); - test.done(); - }, -} + describe('level', () => { + it('both INFO and LOGINFO are log levels', () => { + assert.equal(this.logger.levels.INFO, 6); + assert.equal(this.logger.levels.LOGINFO, 6); + }) -exports.colors = { - setUp : _set_up, - tearDown : _tear_down, - 'colors' (test) { - test.expect(1); - test.ok(this.logger.colors); - test.done(); - }, - 'colorize' (test) { - test.expect(4); - test.ok(this.logger.colorize); - test.equal('function', typeof this.logger.colorize); - test.equal('error', this.logger.colorize('bad-color', 'error')); - const expected = util.inspect.colors ? '\u001b[34mgood\u001b[39m' : 'good'; - test.equal(expected, this.logger.colorize('blue', 'good')); - test.done(); - }, -} + }) -exports.log_if_level = { - setUp : _set_up, - tearDown : _tear_down, - 'log_if_level is a function' (test) { - test.expect(1); - test.ok('function' === typeof this.logger.log_if_level); - test.done(); - }, - 'log_if_level test log entry' (test) { - test.expect(5); - this.logger.loglevel = 9; - const f = this.logger.log_if_level('INFO', 'LOGINFO'); - test.ok(f); - test.ok('function' === typeof f); - test.ok(f('test info message')); - test.equal(1, this.logger.deferred_logs.length); - // console.log(this.logger.deferred_logs[0]); - test.equal('INFO', this.logger.deferred_logs[0].level); - test.done(); - }, - 'log_if_level null case' (test) { - test.expect(2); - this.logger.loglevel = 9; - const f = this.logger.log_if_level('INFO', 'LOGINFO'); - test.ok(f(null)); - test.equal(2, this.logger.deferred_logs.length); - test.done(); - }, - 'log_if_level false' (test) { - test.expect(2); - this.logger.loglevel = 9; - const f = this.logger.log_if_level('INFO', 'LOGINFO'); - test.ok(f(false)); - test.equal(3, this.logger.deferred_logs.length); - test.done(); - }, - 'log_if_level 0' (test) { - test.expect(2); - this.logger.loglevel = 9; - const f = this.logger.log_if_level('INFO', 'LOGINFO'); - test.ok(f(0)); - test.equal(4, this.logger.deferred_logs.length); - test.done(); - }, -} + describe('set_format', () => { + it('set format to DEFAULT', () => { + this.logger.format = ''; + this.logger.set_format('DEFAULT'); + assert.equal(this.logger.format, this.logger.formats.DEFAULT); + }) -exports.add_log_methods = { - setUp : _set_up, - tearDown : _tear_down, - 'ignores non-objects' (test) { - test.expect(2); - test.equal(undefined, this.logger.add_log_methods('')); - test.equal(undefined, this.logger.add_log_methods(function foo (){})); - test.done(); - }, - 'adds functions to an object' (test) { - const testObj = {}; - this.logger.add_log_methods(testObj); - const levels = ['DATA','PROTOCOL','DEBUG','INFO','NOTICE','WARN','ERROR','CRIT','ALERT','EMERG']; - test.expect(levels.length); - for (const level of levels) { - test.ok('function' === typeof(testObj[`log${level.toLowerCase()}`])); - } - test.done(); - }, -} + it('set format to LOGFMT', () => { + this.logger.format = ''; + this.logger.set_format('LOGFMT'); + assert.equal(this.logger.format, this.logger.formats.LOGFMT); + }) + + it('set format to JSON', () => { + this.logger.format = ''; + this.logger.set_format('JSON'); + assert.equal(this.logger.format, this.logger.formats.JSON); + }) + + it('set format to DEFAULT if empty', () => { + this.logger.format = ''; + this.logger.set_format(''); + assert.equal(this.logger.format, this.logger.formats.DEFAULT); + }) + + it('set format to DEFAULT if lowercase', () => { + this.logger.format = ''; + this.logger.set_format('default'); + assert.equal(this.logger.format, this.logger.formats.DEFAULT); + }) + + it('set format to DEFAULT if invalid', () => { + this.logger.format = ''; + this.logger.set_format('invalid'); + assert.equal(this.logger.format, this.logger.formats.DEFAULT); + }) + + }) + + describe('set_loglevel', () => { + it('set loglevel to LOGINFO', () => { + this.logger.set_loglevel('LOGINFO'); + assert.equal(this.logger.loglevel, this.logger.levels.LOGINFO); + }) + + it('set loglevel to INFO', () => { + this.logger.set_loglevel('INFO'); + assert.equal(this.logger.loglevel, this.logger.levels.INFO); + }) + + it('set loglevel to EMERG', () => { + this.logger.set_loglevel('emerg'); + assert.equal(this.logger.loglevel, this.logger.levels.EMERG); + }) + + it('set loglevel to 6', () => { + this.logger.set_loglevel(6); + assert.equal(this.logger.loglevel, 6); + }) + + it('set loglevel to WARN if invalid', () => { + this.logger.set_loglevel('invalid'); + assert.equal(this.logger.loglevel, this.logger.levels.WARN); + }) + }) + + describe('set_timestamps', () => { + it('set timestamps to false', () => { + this.logger.timestamps = undefined; + this.logger.set_timestamps(false); + assert.equal(this.logger.timestamps, false); + }) + + it('set timestamps to true', () => { + this.logger.timestamps = undefined; + this.logger.set_timestamps(true); + assert.equal(this.logger.timestamps, true); + }) + }) + + describe('would_log', () => { + it('should', () => { + this.logger.loglevel = 4; + assert.equal(false, this.logger.would_log(7)); + assert.equal(false, this.logger.would_log(6)); + assert.equal(false, this.logger.would_log(5)); + }) + + it('should not', () => { + this.logger.loglevel = 4; + assert.equal(true, this.logger.would_log(4)); + assert.equal(true, this.logger.would_log(3)); + assert.equal(true, this.logger.would_log(2)); + assert.equal(true, this.logger.would_log(1)); + }) + }) + + describe('log_respond', () => { + it('invalid retval', () => { + assert.equal(false, this.logger.log_respond(901)); + }) + + it('valid retval', () => { + const data = { level: 'INFO', data: "test data" }; + assert.equal(true, this.logger.log_respond(900, 'test msg', data)); + }) + }) + + describe('dump_logs', () => { + it('empty', () => { + assert.ok(this.logger.dump_logs(0)); + }) + + it('with deferred', () => { + this.logger.deferred_logs.push( { level: 'info', data: 'test info'} ); + this.logger.deferred_logs.push( { level: 'INFO', data: 'test info, color'} ); + this.logger.deferred_logs.push( { level: 'WARN', data: 'test warn, color'} ); + assert.ok(this.logger.dump_logs(0)); + assert.ok(this.logger.deferred_logs.length === 0); + }) + }) + + describe('colors', () => { + it('colors', () => { + assert.ok(this.logger.colors); + }) + + it('colorize', () => { + assert.ok(this.logger.colorize); + assert.equal('function', typeof this.logger.colorize); + assert.equal('error', this.logger.colorize('bad-color', 'error')); + const expected = util.inspect.colors ? '\u001b[34mgood\u001b[39m' : 'good'; + assert.equal(expected, this.logger.colorize('blue', 'good')); + }) + }) + + describe('log_if_level', () => { + it('log_if_level is a function', () => { + assert.ok('function' === typeof this.logger.log_if_level); + }) + + it('log_if_level test log entry', () => { + this.logger.loglevel = 9; + const f = this.logger.log_if_level('INFO', 'LOGINFO'); + assert.ok(f); + assert.ok('function' === typeof f); + assert.ok(f('test info message')); + assert.equal(1, this.logger.deferred_logs.length); + // console.log(this.logger.deferred_logs[0]); + assert.equal('INFO', this.logger.deferred_logs[0].level); + }) + + it('log_if_level null case', () => { + this.logger.loglevel = 9; + const f = this.logger.log_if_level('INFO', 'LOGINFO'); + assert.ok(f(null)); + assert.equal(2, this.logger.deferred_logs.length); + }) + + it('log_if_level false', () => { + this.logger.loglevel = 9; + const f = this.logger.log_if_level('INFO', 'LOGINFO'); + assert.ok(f(false)); + assert.equal(3, this.logger.deferred_logs.length); + }) + + it('log_if_level 0', () => { + this.logger.loglevel = 9; + const f = this.logger.log_if_level('INFO', 'LOGINFO'); + assert.ok(f(0)); + assert.equal(4, this.logger.deferred_logs.length); + }) + }) + + describe('add_log_methods', () => { + it('ignores non-objects', () => { + assert.equal(undefined, this.logger.add_log_methods('')); + assert.equal(undefined, this.logger.add_log_methods(function foo (){})); + }) + + it('adds functions to an object', () => { + const testObj = {}; + this.logger.add_log_methods(testObj); + const levels = ['DATA','PROTOCOL','DEBUG','INFO','NOTICE','WARN','ERROR','CRIT','ALERT','EMERG']; + for (const level of levels) { + assert.ok('function' === typeof(testObj[`log${level.toLowerCase()}`])); + } + }) + }) +}) \ No newline at end of file diff --git a/tests/outbound/hmail.js b/tests/outbound/hmail.js index f6ba48f17..05b57470b 100644 --- a/tests/outbound/hmail.js +++ b/tests/outbound/hmail.js @@ -6,85 +6,115 @@ const path = require('path') const Hmail = require('../../outbound/hmail'); const outbound = require('../../outbound/index'); -exports.HMailItem = { - 'normal queue file' (test) { - test.expect(1); + +describe('outbound/hmail', () => { + beforeEach((done) => { + this.hmail = new Hmail('1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', 'tests/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', {}); + done() + }) + + it('sort_mx', (done) => { + const sorted = this.hmail.sort_mx([ + { exchange: 'mx2.example.com', priority: 5 }, + { exchange: 'mx1.example.com', priority: 6 }, + ]) + assert.equal(sorted[0].exchange, 'mx2.example.com') + done() + }) + it('sort_mx, shuffled', (done) => { + const sorted = this.hmail.sort_mx([ + { exchange: 'mx2.example.com', priority: 5 }, + { exchange: 'mx1.example.com', priority: 6 }, + { exchange: 'mx3.example.com', priority: 6 }, + ]) + assert.equal(sorted[0].exchange, 'mx2.example.com') + assert.ok(sorted[1].exchange == 'mx3.example.com' || sorted[1].exchange == 'mx1.example.com') + done() + }) + it('force_tls', (done) => { + this.hmail.todo = { domain: 'miss.example.com' } + this.hmail.obtls.cfg = { force_tls_hosts: ['1.2.3.4', 'hit.example.com'] } + assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.4' }), true) + assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), false) + this.hmail.todo = { domain: 'hit.example.com' } + assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), true) + done() + }) +}) + +describe('outbound/hmail.HMailItem', () => { + it('normal queue file', (done) => { this.hmail = new Hmail('1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', 'tests/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', {}); this.hmail.on('ready', () => { // console.log(this.hmail); - test.ok(this.hmail) - test.done() + assert.ok(this.hmail) + done() }) this.hmail.on('error', err => { console.log(err) - test.equal(err, undefined) - test.done() + assert.equal(err, undefined) + done() }) - }, - 'normal TODO w/multibyte chars loads w/o error' (test) { - test.expect(1); + }) + it('normal TODO w/multibyte chars loads w/o error', (done) => { this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_1_qfile', 'tests/fixtures/todo_qfile.txt', {}); this.hmail.on('ready', () => { // console.log(this.hmail); - test.ok(this.hmail) - test.done() + assert.ok(this.hmail) + done() }) this.hmail.on('error', err => { console.log(err) - test.equal(err, undefined) - test.done() + assert.equal(err, undefined) + done() }) - }, - 'too short TODO length declared' (test) { - test.expect(1); + }) + it('too short TODO length declared', (done) => { this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka', 'tests/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka', {}); this.hmail.on('ready', () => { // console.log(this.hmail); - test.ok(this.hmail) - test.done(); + assert.ok(this.hmail) + done(); }) this.hmail.on('error', (err) => { console.log(err); - test.ok(err); - test.done(); + assert.ok(err); + done(); }) - }, - 'too long TODO length declared' (test) { - test.expect(1); + }) + it('too long TODO length declared', (done) => { this.hmail = new Hmail('1508269674999_1508269674999_0_34002_socVUF_1_haraka', 'tests/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka', {}); this.hmail.on('ready', () => { // console.log(this.hmail); - test.ok(this.hmail) - test.done(); + assert.ok(this.hmail) + done(); }) this.hmail.on('error', (err) => { console.log(err); - test.ok(err); - test.done(); + assert.ok(err); + done(); }) - }, - 'zero-length file load skip w/o crash' (test) { - test.expect(1); + }) + it('zero-length file load skip w/o crash', (done) => { this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_zero', 'tests/queue/zero-length', {}); this.hmail.on('ready', () => { - test.ok(this.hmail) - test.done(); + assert.ok(this.hmail) + done(); }) this.hmail.on('error', (err) => { console.error(err); - test.ok(err); - test.done(); + assert.ok(err); + done(); }) - }, - 'lifecycle, reads and writes a haraka queue file' (test) { - test.expect(1); + }) + it('lifecycle, reads and writes a haraka queue file', (done) => { this.hmail = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_qfile', 'tests/fixtures/todo_qfile.txt', {}); this.hmail.on('error', (err) => { // console.log(err); - test.equals(err, undefined); - test.done(); + assert.equals(err, undefined); + done(); }) this.hmail.on('ready', () => { @@ -95,51 +125,17 @@ exports.HMailItem = { outbound.build_todo(this.hmail.todo, ws, () => { // console.log('returned from build_todo, piping') // console.log(this.hmail.todo) - // test.equals(this.hmail.todo.message_stream.headers.length, 22); + // assert.equals(this.hmail.todo.message_stream.headers.length, 22); const ds = this.hmail.data_stream() ds.pipe(ws, { dot_stuffing: true }); ws.on('close', () => { // console.log(this.hmail.todo) - test.equal(fs.statSync(tmpfile).size, 4204); - test.done(); + assert.equal(fs.statSync(tmpfile).size, 4204); + done(); }) }) }) - }, -} - -exports.hmail = { - setUp: function (done) { - this.hmail = new Hmail('1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', 'tests/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka', {}); - done() - }, - 'sort_mx' (test) { - const sorted = this.hmail.sort_mx([ - { exchange: 'mx2.example.com', priority: 5 }, - { exchange: 'mx1.example.com', priority: 6 }, - ]) - assert.equal(sorted[0].exchange, 'mx2.example.com') - test.done() - }, - 'sort_mx, shuffled' (test) { - const sorted = this.hmail.sort_mx([ - { exchange: 'mx2.example.com', priority: 5 }, - { exchange: 'mx1.example.com', priority: 6 }, - { exchange: 'mx3.example.com', priority: 6 }, - ]) - assert.equal(sorted[0].exchange, 'mx2.example.com') - assert.ok(sorted[1].exchange == 'mx3.example.com' || sorted[1].exchange == 'mx1.example.com') - test.done() - }, - 'force_tls' (test) { - this.hmail.todo = { domain: 'miss.example.com' } - this.hmail.obtls.cfg = { force_tls_hosts: ['1.2.3.4', 'hit.example.com'] } - assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.4' }), true) - assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), false) - this.hmail.todo = { domain: 'hit.example.com' } - assert.equal(this.hmail.get_force_tls({ exchange: '1.2.3.5' }), true) - test.done() - } -} \ No newline at end of file + }) +}) diff --git a/tests/outbound/index.js b/tests/outbound/index.js index 1ecbf6035..069795bbb 100644 --- a/tests/outbound/index.js +++ b/tests/outbound/index.js @@ -1,8 +1,8 @@ - 'use strict'; + +const assert = require('node:assert') const fs = require('fs'); const path = require('path'); -const os = require('os'); const constants = require('haraka-constants'); const logger = require('../../logger'); @@ -16,9 +16,9 @@ const lines = [ '', ]; -exports.outbound = { - 'converts \\n and \\r\\n line endings to \\r\\n' : test => { - test.expect(2); +describe('outbound', () => { + + it('converts \\n and \\r\\n line endings to \\r\\n' , () => { for (const ending of ['\n', '\r\n']) { let contents = lines.join(ending); @@ -30,7 +30,6 @@ exports.outbound = { while ((match = re.exec(contents))) { let line = match[1]; line = line.replace(/\r?\n?$/, '\r\n'); // assure \r\n ending - // transaction.add_data(new Buffer(line)); result += line; contents = contents.substr(match[1].length); if (contents.length === 0) { @@ -38,285 +37,184 @@ exports.outbound = { } } - test.deepEqual(lines.join('\r\n'), result); + assert.deepEqual(lines.join('\r\n'), result); } - test.done(); - }, - 'log_methods added': test => { + }) + + it('log_methods added', () => { const levels = ['DATA','PROTOCOL','DEBUG','INFO','NOTICE','WARN','ERROR','CRIT','ALERT','EMERG'] - test.expect(levels.length); const HMailItem = require('../../outbound/hmail'); for (const level of levels) { - test.ok(HMailItem.prototype[`log${level.toLowerCase()}`], `Log method for level: ${level}`); + assert.ok(HMailItem.prototype[`log${level.toLowerCase()}`], `Log method for level: ${level}`); } - test.done(); - }, - 'set_temp_fail_intervals coverage': test => { - test.expect(5); + }) + + it('set_temp_fail_intervals coverage', () => { const config = require('../../outbound/config'); // Test default configuration - test.deepEqual(config.cfg.temp_fail_intervals, [64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072]); + assert.deepEqual(config.cfg.temp_fail_intervals, [64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072]); // Test a simple configuration config.cfg.temp_fail_intervals = '10s, 1m*2'; config.set_temp_fail_intervals(); - test.deepEqual(config.cfg.temp_fail_intervals, [10, 60, 60]); + assert.deepEqual(config.cfg.temp_fail_intervals, [10, 60, 60]); // Test a complex configuration config.cfg.temp_fail_intervals = '30s, 1m, 5m, 9m, 15m*3, 30m*2, 1h*3, 2h*3, 1d'; config.set_temp_fail_intervals(); - test.deepEqual(config.cfg.temp_fail_intervals, [30,60,300,540,900,900,900,1800,1800,3600,3600,3600,7200,7200,7200,86400]); + assert.deepEqual(config.cfg.temp_fail_intervals, [30,60,300,540,900,900,900,1800,1800,3600,3600,3600,7200,7200,7200,86400]); // Test the "none" configuration config.cfg.temp_fail_intervals = 'none'; config.set_temp_fail_intervals(); - test.deepEqual(config.cfg.temp_fail_intervals, []); + assert.deepEqual(config.cfg.temp_fail_intervals, []); // Test bad config (should revert to default) config.cfg.temp_fail_intervals = '60 min'; config.set_temp_fail_intervals(); - test.deepEqual(config.cfg.temp_fail_intervals, [64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072]); - test.done(); - } -} - -exports.qfile = { - setUp (done) { - this.qfile = require('../../outbound').qfile; - done(); - }, - 'name() basic functions' (test){ - test.expect(3); - const name = this.qfile.name(); - const split = name.split('_'); - test.equal(split.length, 7); - test.equal(split[2], 0); - test.equal(split[3], process.pid); - test.done(); - }, - 'name() with overrides' (test){ - test.expect(7); - const overrides = { - arrival : 12345, - next_attempt : 12345, - attempts : 15, - pid : process.pid, - uid : 'XXYYZZ', - host : os.hostname(), - }; - const name = this.qfile.name(overrides); - const split = name.split('_'); - test.equal(split.length, 7); - test.equal(split[0], overrides.arrival); - test.equal(split[1], overrides.next_attempt); - test.equal(split[2], overrides.attempts); - test.equal(split[3], overrides.pid); - test.equal(split[4], overrides.uid); - test.equal(split[6], overrides.host); - test.done(); - }, - 'rnd_unique() is unique-ish' (test){ - const repeats = 1000; - test.expect(repeats); - const u = this.qfile.rnd_unique(); - for (let i = 0; i < repeats; i++){ - test.notEqual(u, this.qfile.rnd_unique()); - } - test.done(); - }, - 'parts() updates previous queue filenames' (test){ - test.expect(4); - // $nextattempt_$attempts_$pid_$uniq.$host - const name = "1111_0_2222_3333.foo.example.com" - const parts = this.qfile.parts(name); - test.equal(parts.next_attempt, 1111); - test.equal(parts.attempts, 0); - test.equal(parts.pid, 2222); - test.equal(parts.host, 'foo.example.com'); - test.done(); - }, - 'parts() handles standard queue filenames' (test){ - test.expect(6); - const overrides = { - arrival : 12345, - next_attempt : 12345, - attempts : 15, - pid : process.pid, - uid : 'XXYYZZ', - host : os.hostname(), - }; - const name = this.qfile.name(overrides); - const parts = this.qfile.parts(name); - test.equal(parts.arrival, overrides.arrival); - test.equal(parts.next_attempt, overrides.next_attempt); - test.equal(parts.attempts, overrides.attempts); - test.equal(parts.pid, overrides.pid); - test.equal(parts.uid, overrides.uid); - test.equal(parts.host, overrides.host); - test.done(); - } -} - -exports.get_tls_options = { - setUp (done) { - process.env.HARAKA_TEST_DIR=path.resolve('tests'); - this.outbound = require('../../outbound'); - this.obtls = require('../../outbound/tls'); - const tls_socket = require('../../tls_socket'); - - // reset config to load from tests directory - const testDir = path.resolve('tests'); - this.outbound.config = this.outbound.config.module_config(testDir); - this.obtls.test_config(tls_socket.config.module_config(testDir), this.outbound.config); - this.obtls.init(done) - }, - tearDown: done => { - delete process.env.HARAKA_TEST_DIR; - done(); - }, - 'gets TLS properties from tls.ini.outbound' (test) { - test.expect(1); - const tls_config = this.obtls.get_tls_options( - { exchange: 'mail.example.com'} - ); - - test.deepEqual(tls_config, { - servername: 'mail.example.com', - key: fs.readFileSync(path.resolve('tests','config','outbound_tls_key.pem')), - cert: fs.readFileSync(path.resolve('tests','config','outbound_tls_cert.pem')), - dhparam: fs.readFileSync(path.resolve('tests','config','dhparams.pem')), - ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', - minVersion: 'TLSv1', - rejectUnauthorized: false, - requestCert: false, - honorCipherOrder: false, - redis: { disable_for_failed_hosts: false }, - no_tls_hosts: [], - force_tls_hosts: ['first.example.com', 'second.example.net'] - }); - test.done(); - }, -} - -exports.build_todo = { - setUp (done) { - this.outbound = require('../../outbound'); - try { - fs.unlinkSync('tests/queue/multibyte'); - fs.unlinkSync('tests/queue/plain'); - } - catch (ignore) {} - done(); - }, - tearDown: done => { - // fs.unlink('tests/queue/multibyte', done); - done(); - }, - 'saves a file' (test) { - const todo = JSON.parse('{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileWithoutAccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileWithoutaccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}'); - const fd = fs.openSync('tests/queue/plain', 'w'); - const ws = new fs.createWriteStream('tests/queue/plain', { fd, flags: constants.WRITE_EXCL }); - ws.on('close', () => { - // console.log(arguments); - test.ok(1); - test.done(); - }) - ws.on('error', (e) => { - console.error(e); - test.done(); - }) - this.outbound.build_todo(todo, ws, () => { - ws.write(Buffer.from('This is the message body')); - fs.fsync(fd, () => { ws.close(); }) + assert.deepEqual(config.cfg.temp_fail_intervals, [64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072]); + }) + + describe('get_tls_options', () => { + beforeEach((done) => { + process.env.HARAKA_TEST_DIR=path.resolve('tests'); + this.outbound = require('../../outbound'); + this.obtls = require('../../outbound/tls'); + const tls_socket = require('../../tls_socket'); + + // reset config to load from tests directory + const testDir = path.resolve('tests'); + this.outbound.config = this.outbound.config.module_config(testDir); + this.obtls.test_config(tls_socket.config.module_config(testDir), this.outbound.config); + this.obtls.init(done) }) - }, - 'saves a file with multibyte chars' (test) { - const todo = JSON.parse('{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileWîthÁccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileWîthÁccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}'); - const fd = fs.openSync('tests/queue/multibyte', 'w'); - const ws = new fs.WriteStream('tests/queue/multibyte', { fd, flags: constants.WRITE_EXCL }); - ws.on('close', () => { - test.ok(1); - test.done(); + + afterEach((done) => { + delete process.env.HARAKA_TEST_DIR; + done(); }) - ws.on('error', (e) => { - console.error(e); - test.done(); + + it('gets TLS properties from tls.ini.outbound', () => { + const tls_config = this.obtls.get_tls_options( + { exchange: 'mail.example.com'} + ); + + assert.deepEqual(tls_config, { + servername: 'mail.example.com', + key: fs.readFileSync(path.resolve('tests','config','outbound_tls_key.pem')), + cert: fs.readFileSync(path.resolve('tests','config','outbound_tls_cert.pem')), + dhparam: fs.readFileSync(path.resolve('tests','config','dhparams.pem')), + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + minVersion: 'TLSv1', + rejectUnauthorized: false, + requestCert: false, + honorCipherOrder: false, + redis: { disable_for_failed_hosts: false }, + no_tls_hosts: [], + force_tls_hosts: ['first.example.com', 'second.example.net'] + }) }) - this.outbound.build_todo(todo, ws, () => { - ws.write(Buffer.from('This is the message body')); - fs.fsync(fd, () => { ws.close(); }) + }) + + describe('build_todo', () => { + beforeEach((done) => { + this.outbound = require('../../outbound'); + try { + fs.unlinkSync('tests/queue/multibyte'); + fs.unlinkSync('tests/queue/plain'); + } + catch (ignore) {} + done(); }) - }, - // '': function (test) { - - // test.done(); - // }, -} -exports.timer_queue = { - setUp (done) { - process.env.HARAKA_TEST_DIR=path.resolve('tests'); - this.outbound = require('../../outbound'); - const TimerQueue = require('../../outbound/timer_queue'); - this.ob_timer_queue = new TimerQueue(500); - done(); - }, - tearDown (done) { - delete process.env.HARAKA_TEST_DIR; - this.ob_timer_queue.shutdown(); - done(); - }, - 'has initial length of 0' (test) { - test.expect(1); + it('saves a file', () => { + const todo = JSON.parse('{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileWithoutAccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileWithoutaccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}'); + const fd = fs.openSync('tests/queue/plain', 'w'); + const ws = new fs.createWriteStream('tests/queue/plain', { fd, flags: constants.WRITE_EXCL }); + ws.on('close', () => { + // console.log(arguments); + assert.ok(1); + }) + ws.on('error', (e) => { + console.error(e); + }) + this.outbound.build_todo(todo, ws, () => { + ws.write(Buffer.from('This is the message body')); + fs.fsync(fd, () => { ws.close(); }) + }) + }) - const tq_length = this.ob_timer_queue.length(); + it('saves a file with multibyte chars', () => { + const todo = JSON.parse('{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileWîthÁccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileWîthÁccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}'); + const fd = fs.openSync('tests/queue/multibyte', 'w'); + const ws = new fs.WriteStream('tests/queue/multibyte', { fd, flags: constants.WRITE_EXCL }); + ws.on('close', () => { + assert.ok(1); + }) + ws.on('error', (e) => { + console.error(e); + }) + this.outbound.build_todo(todo, ws, () => { + ws.write(Buffer.from('This is the message body')); + fs.fsync(fd, () => { ws.close(); }) + }) + }) + }) + + describe('timer_queue', () => { + beforeEach((done) => { + process.env.HARAKA_TEST_DIR=path.resolve('tests'); + this.outbound = require('../../outbound'); + const TimerQueue = require('../../outbound/timer_queue'); + this.ob_timer_queue = new TimerQueue(500); + done(); + }) - test.equal(tq_length, 0); - test.done(); - }, - 'can add items' (test) { - test.expect(1); + afterEach((done) => { + delete process.env.HARAKA_TEST_DIR; + this.ob_timer_queue.shutdown(); + done() + }) - this.ob_timer_queue.add("1", 1000); - this.ob_timer_queue.add("2", 2000); + it('has initial length of 0', () => { + assert.equal(this.ob_timer_queue.length(), 0); + }) - const tq_length = this.ob_timer_queue.length(); + it('can add items', () => { + this.ob_timer_queue.add("1", 1000); + this.ob_timer_queue.add("2", 2000); - test.equal(tq_length, 2); - test.done(); - }, - 'can drain items' (test) { - test.expect(2); + assert.equal(this.ob_timer_queue.length(), 2); + }) - this.ob_timer_queue.add("1", 1000); - this.ob_timer_queue.add("2", 2000); + it('can drain items', () => { - let tq_length = this.ob_timer_queue.length(); + this.ob_timer_queue.add("1", 1000); + this.ob_timer_queue.add("2", 2000); - test.equal(tq_length, 2); + let tq_length = this.ob_timer_queue.length(); - this.ob_timer_queue.drain(); - tq_length = this.ob_timer_queue.length(); + assert.equal(tq_length, 2); - test.equal(tq_length, 0); + this.ob_timer_queue.drain(); + tq_length = this.ob_timer_queue.length(); - test.done(); - }, - 'can discard items by id' (test) { - test.expect(3); + assert.equal(tq_length, 0); + }) - this.ob_timer_queue.add("1", 1000); - this.ob_timer_queue.add("2", 2000); + it('can discard items by id', () => { - let tq_length = this.ob_timer_queue.length(); + this.ob_timer_queue.add("1", 1000); + this.ob_timer_queue.add("2", 2000); - test.equal(tq_length, 2); + let tq_length = this.ob_timer_queue.length(); - this.ob_timer_queue.discard("2"); - tq_length = this.ob_timer_queue.length(); + assert.equal(tq_length, 2); - test.equal(tq_length, 1); - test.equal(this.ob_timer_queue.queue[0].id, "1"); + this.ob_timer_queue.discard("2"); + tq_length = this.ob_timer_queue.length(); - test.done(); - } -} + assert.equal(tq_length, 1); + assert.equal(this.ob_timer_queue.queue[0].id, "1"); + }) + }) +}) diff --git a/tests/outbound/qfile.js b/tests/outbound/qfile.js index f052ec5d2..3dd8dffc4 100644 --- a/tests/outbound/qfile.js +++ b/tests/outbound/qfile.js @@ -1,67 +1,126 @@ +const assert = require('node:assert') +const os = require('node:os'); -const qfile = require('../../outbound/qfile'); +describe('qfile', () => { -exports.parts = { - 'handles 4': (test) => { - test.expect(1) - const r = qfile.parts('1484878079415_0_12345_8888.mta1.example.com') - // console.log(r); - delete r.arrival - delete r.uid - delete r.counter - test.deepEqual(r, { - next_attempt: 1484878079415, - attempts: 0, - pid: 12345, - host: 'mta1.example.com', - age: 0 + describe('qfile', () => { + beforeEach((done) => { + this.qfile = require('../../outbound/qfile') + done(); }) - test.done() - }, - 'handles 7': (test) => { - test.expect(1) - const r = qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz_1_haraka') - // console.log(r); - delete r.age; - test.deepEqual(r, { - arrival: 1516650518128, - next_attempt: 1516667073032, - attempts: 8, - pid: 29538, - uid: 'TkPZWz', - counter: 1, - host: 'haraka', + + it('name() basic functions', () => { + const name = this.qfile.name(); + const split = name.split('_'); + assert.equal(split.length, 7); + assert.equal(split[2], 0); + assert.equal(split[3], process.pid); + }) + + it('name() with overrides', () => { + const overrides = { + arrival : 12345, + next_attempt : 12345, + attempts : 15, + pid : process.pid, + uid : 'XXYYZZ', + host : os.hostname(), + }; + const name = this.qfile.name(overrides); + const split = name.split('_'); + assert.equal(split.length, 7); + assert.equal(split[0], overrides.arrival); + assert.equal(split[1], overrides.next_attempt); + assert.equal(split[2], overrides.attempts); + assert.equal(split[3], overrides.pid); + assert.equal(split[4], overrides.uid); + assert.equal(split[6], overrides.host); }) - test.done() - }, - 'punts on 5': (test) => { - test.expect(1) - const r = qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz') - test.deepEqual(r, null) - test.done() - }, -} -exports.hostname = { - 'hostname, defaults to os.hostname()': test => { - test.expect(1) - const r = qfile.hostname(); - // console.log(r) - test.deepEqual(r, require('os').hostname()) - test.done() - }, - 'hostname, replaces \\ char': test => { - test.expect(1) - const r = qfile.hostname('mt\\a1.exam\\ple.com') - // console.log(r) - test.deepEqual(r, 'mt\\057a1.exam\\057ple.com') - test.done() - }, - 'hostname, replaces _ char': test => { - test.expect(1) - const r = qfile.hostname('mt_a1.exam_ple.com') - // console.log(r) - test.deepEqual(r, 'mt\\137a1.exam\\137ple.com') - test.done() - } -} + it('rnd_unique() is unique-ish', () => { + const repeats = 1000; + const u = this.qfile.rnd_unique(); + for (let i = 0; i < repeats; i++){ + assert.notEqual(u, this.qfile.rnd_unique()); + } + }) + }) + + describe('parts', () => { + + it('parts() updates previous queue filenames', () => { + // $nextattempt_$attempts_$pid_$uniq.$host + const name = "1111_0_2222_3333.foo.example.com" + const parts = this.qfile.parts(name); + assert.equal(parts.next_attempt, 1111); + assert.equal(parts.attempts, 0); + assert.equal(parts.pid, 2222); + assert.equal(parts.host, 'foo.example.com'); + }) + + it('parts() handles standard queue filenames', () => { + const overrides = { + arrival : 12345, + next_attempt : 12345, + attempts : 15, + pid : process.pid, + uid : 'XXYYZZ', + host : os.hostname(), + }; + const name = this.qfile.name(overrides); + const parts = this.qfile.parts(name); + assert.equal(parts.arrival, overrides.arrival); + assert.equal(parts.next_attempt, overrides.next_attempt); + assert.equal(parts.attempts, overrides.attempts); + assert.equal(parts.pid, overrides.pid); + assert.equal(parts.uid, overrides.uid); + assert.equal(parts.host, overrides.host); + }) + + it('handles 4', () => { + const r = this.qfile.parts('1484878079415_0_12345_8888.mta1.example.com') + delete r.arrival + delete r.uid + delete r.counter + assert.deepEqual(r, { + next_attempt: 1484878079415, + attempts: 0, + pid: 12345, + host: 'mta1.example.com', + age: 0 + }) + }) + + it('handles 7', () => { + const r = this.qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz_1_haraka') + delete r.age; + assert.deepEqual(r, { + arrival: 1516650518128, + next_attempt: 1516667073032, + attempts: 8, + pid: 29538, + uid: 'TkPZWz', + counter: 1, + host: 'haraka', + }) + }) + + it('punts on 5', () => { + assert.deepEqual(this.qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz'), null) + }) + }) + + describe('hostname', () => { + it('hostname, defaults to os.hostname()', () => { + assert.deepEqual(this.qfile.hostname(), require('os').hostname()) + }) + + it('hostname, replaces \\ char', () => { + assert.deepEqual(this.qfile.hostname('mt\\a1.exam\\ple.com'), 'mt\\057a1.exam\\057ple.com') + }) + + it('hostname, replaces _ char', () => { + assert.deepEqual(this.qfile.hostname('mt_a1.exam_ple.com'), 'mt\\137a1.exam\\137ple.com') + }) + }) +}) \ No newline at end of file diff --git a/tests/outbound_bounce_net_errors.js b/tests/outbound_bounce_net_errors.js index 2711000b5..cb3306110 100644 --- a/tests/outbound_bounce_net_errors.js +++ b/tests/outbound_bounce_net_errors.js @@ -8,9 +8,10 @@ // - Talk some STMP in the playbook // - Test the outcome by replacing trigger functions with our testing code (outbound.send_email, HMailItem.temp_fail, ...) -const dns = require('dns'); -const fs = require('fs'); -const path = require('path'); +const assert = require('node:assert') +const dns = require('node:dns'); +const fs = require('node:fs'); +const path = require('node:path'); const constants = require('haraka-constants'); const util_hmailitem = require('./fixtures/util_hmailitem'); @@ -25,8 +26,8 @@ const outbound_context = { const queue_dir = path.resolve(__dirname, 'test-queue'); -exports.bounce_3464 = { - setUp : done => { +describe('outbound_bounce_net_errors', () => { + beforeEach((done) => { fs.exists(queue_dir, exists => { if (exists) { done(); @@ -35,116 +36,107 @@ exports.bounce_3464 = { fs.mkdir(queue_dir, done) } }); - }, - tearDown: done => { - fs.exists(queue_dir, exists => { + }) + + afterEach((done) => { + fs.exists(queue_dir, (exists) => { if (exists) { - const files = fs.readdirSync(queue_dir); - files.forEach((file,index) => { + for (const file of fs.readdirSync(queue_dir)) { const curPath = path.resolve(queue_dir, file); - if (fs.lstatSync(curPath).isDirectory()) { // recurse - return done(new Error(`did not expect an sub folder here ("${curPath}")! cancel`)); + if (fs.lstatSync(curPath).isDirectory()) { + console.error(`did not expect an sub folder here ("${curPath}")! cancel`) } - }); - files.forEach((file,index) => { - const curPath = path.resolve(queue_dir, file); fs.unlinkSync(curPath); - }); + } } - done(); - }); - }, - 'test get-mx-deny triggers bounce(...)': test => { - test.expect(2); + done() + }) + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test get-mx-deny triggers bounce(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const orig_bounce = HMailItem.prototype.bounce; HMailItem.prototype.bounce = function (err, opts) { - test.ok(true, 'get_mx=DENY: bounce function called'); + assert.ok(true, 'get_mx=DENY: bounce function called'); /* dsn_code: 550, dsn_status: '5.1.2', dsn_action: 'failed' */ - test.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx=DENY dsn status = 5.1.2'); + assert.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx=DENY dsn status = 5.1.2'); + done() }; mock_hmail.domain = mock_hmail.todo.domain; HMailItem.prototype.get_mx_respond.apply(mock_hmail, [constants.deny, {}]); HMailItem.prototype.bounce = orig_bounce; - test.done(); - }); - }, - 'test get-mx-denysoft triggers temp_fail(...)': test => { - test.expect(2); + }) + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test get-mx-denysoft triggers temp_fail(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'get_mx-DENYSOFT: temp_fail function called'); + assert.ok(true, 'get_mx-DENYSOFT: temp_fail function called'); /*dsn_code: 450, dsn_status: '4.1.2', dsn_action: 'delayed' */ - test.equal('4.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx=DENYSOFT dsn status = 4.1.2'); + assert.equal('4.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx=DENYSOFT dsn status = 4.1.2'); + done() }; mock_hmail.domain = mock_hmail.todo.domain; HMailItem.prototype.get_mx_respond.apply(mock_hmail, [constants.denysoft, {}]); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); - }); - }, - 'test found_mx({code:dns.NXDOMAIN}) triggers bounce(...)': test => { - test.expect(2); + }) + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test found_mx({code:dns.NXDOMAIN}) triggers bounce(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const orig_bounce = HMailItem.prototype.bounce; HMailItem.prototype.bounce = function (err, opts) { - test.ok(true, 'get_mx_error({code: dns.NXDOMAIN}): bounce function called'); - test.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx_error({code: dns.NXDOMAIN}: dsn status = 5.1.2'); + assert.ok(true, 'get_mx_error({code: dns.NXDOMAIN}): bounce function called'); + assert.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'get_mx_error({code: dns.NXDOMAIN}: dsn status = 5.1.2'); + done() }; HMailItem.prototype.get_mx_error.apply(mock_hmail, [{code: dns.NXDOMAIN}]); HMailItem.prototype.bounce = orig_bounce; - test.done(); }); - }, - 'test get_mx_error({code:\'SOME-OTHER-ERR\'}) triggers temp_fail(...)': test => { - test.expect(2); + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test get_mx_error({code:\'SOME-OTHER-ERR\'}) triggers temp_fail(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'get_mx_error({code: "SOME-OTHER-ERR"}): temp_fail function called'); - test.equal('4.1.0', this.todo.rcpt_to[0].dsn_status, 'get_mx_error({code: "SOME-OTHER-ERR"}: dsn status = 4.1.0'); + assert.ok(true, 'get_mx_error({code: "SOME-OTHER-ERR"}): temp_fail function called'); + assert.equal('4.1.0', this.todo.rcpt_to[0].dsn_status, 'get_mx_error({code: "SOME-OTHER-ERR"}: dsn status = 4.1.0'); + done() }; HMailItem.prototype.get_mx_error.apply(mock_hmail, [{code: 'SOME-OTHER-ERR'}, {}]); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); }); - }, - 'test found_mx(null, [{priority:0,exchange:\'\'}]) triggers bounce(...)': test => { - test.expect(2); + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test found_mx(null, [{priority:0,exchange:\'\'}]) triggers bounce(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const orig_bounce = HMailItem.prototype.bounce; HMailItem.prototype.bounce = function (err, opts) { - test.ok(true, 'found_mx(null, [{priority:0,exchange:""}]): bounce function called'); - test.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'found_mx(null, [{priority:0,exchange:""}]): dsn status = 5.1.2'); + assert.ok(true, 'found_mx(null, [{priority:0,exchange:""}]): bounce function called'); + assert.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'found_mx(null, [{priority:0,exchange:""}]): dsn status = 5.1.2'); + done() }; HMailItem.prototype.found_mx.apply(mock_hmail, [[{priority:0,exchange:''}]]); HMailItem.prototype.bounce = orig_bounce; - test.done(); }); - }, - 'test try_deliver while hmail.mxlist=[] triggers bounce(...)': test => { - test.expect(2); + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + it('test try_deliver while hmail.mxlist=[] triggers bounce(...)', (done) => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { mock_hmail.mxlist = []; const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'try_deliver while hmail.mxlist=[]: temp_fail function called'); - test.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'try_deliver while hmail.mxlist=[]: dsn status = 5.1.2'); + assert.ok(true, 'try_deliver while hmail.mxlist=[]: temp_fail function called'); + assert.equal('5.1.2', this.todo.rcpt_to[0].dsn_status, 'try_deliver while hmail.mxlist=[]: dsn status = 5.1.2'); + done() }; HMailItem.prototype.try_deliver.apply(mock_hmail, []); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); }); - }, -} + }) +}) diff --git a/tests/outbound_bounce_rfc3464.js b/tests/outbound_bounce_rfc3464.js index 90049c363..c8370a382 100644 --- a/tests/outbound_bounce_rfc3464.js +++ b/tests/outbound_bounce_rfc3464.js @@ -1,16 +1,17 @@ 'use strict'; -// Testing bounce email contents related to errors occuring during STMP dialog +// Testing bounce email contents related to errors occuring during SMTP dialog // About running the tests: // - Making a folder for queuing files // - Creating a HMailItem instance using fixtures/util_hmailitem -// - Talk some STMP in the playbook +// - Talk some SMTP in the playbook // - Test the outcome by replacing trigger functions with our testing code (outbound.send_email, HMailItem.temp_fail, ...) // At one point, the mocked remote SMTP says "5XX" or "4XX" and we test that // * outbound.send_email is called with a RFC3464 bounce message // * or, in case of 4XX: that temp_fail is called and dsn vars are available) +const assert = require('node:assert') const fs = require('fs'); const path = require('path'); @@ -30,62 +31,55 @@ const outbound_context = { const queue_dir = path.resolve(__dirname, 'test-queue'); -exports.bounce_3464 = { - setUp : done => { +describe('outbound_bounce_rfc3464', () => { + beforeEach((done) => { fs.exists(queue_dir, exists => { - if (exists) { - done(); - } - else { - fs.mkdir(queue_dir, err => { - if (err) { - return done(err); - } - done(); - }); - } - }); - }, - tearDown: done => { - fs.exists(queue_dir, exists => { - if (exists) { - const files = fs.readdirSync(queue_dir); - files.forEach((file,index) => { - const curPath = path.resolve(queue_dir, file); - if (fs.lstatSync(curPath).isDirectory()) { // recurse - return done(new Error(`did not expect an sub folder here ("${curPath}")! cancel`)); - } - }); - files.forEach((file,index) => { - const curPath = path.resolve(queue_dir, file); - fs.unlinkSync(curPath); - }); - done(); - } - else { + if (exists) return done(); + + fs.mkdir(queue_dir, err => { + if (err) return done(err); done(); - } - }); - }, - 'test MAIL FROM responded with 500 5.0.0 triggers send_email() containing bounce msg with codes and message': test => { - test.expect(9); + }) + }) + }) - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + afterEach((done) => { + fs.exists(queue_dir, exists => { + if (!exists) return done() + + const files = fs.readdirSync(queue_dir); + files.forEach((file,index) => { + const curPath = path.resolve(queue_dir, file); + if (fs.lstatSync(curPath).isDirectory()) { // recurse + return done(new Error(`did not expect an sub folder here ("${curPath}")! cancel`)); + } + }) + files.forEach((file,index) => { + const curPath = path.resolve(queue_dir, file); + fs.unlinkSync(curPath); + }) + done(); + }) + }) + + it('test MAIL FROM responded with 500 5.0.0 triggers send_email() containing bounce msg with codes and message', (done) => { + + util_hmailitem.newMockHMailItem(outbound_context, done, {}, (mock_hmail) => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_send_email = outbound_context.exports.send_email; + outbound_context.exports.send_email = (from, to, contents, cb, opts) => { - test.ok(true, 'outbound.send_email called'); - test.ok(contents.match(/^Content-type: message\/delivery-status/m), 'its a bounce report'); - test.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'bounce report contains final recipient'); - test.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); - test.ok(contents.match(/^Status: 5\.0\.0/m), 'bounce report contains status field with ext. smtp code'); - test.ok(contents.match(/Absolutely not acceptable\. Basic Test Only\./), 'original upstream message available'); + assert.ok(true, 'outbound.send_email called'); + assert.ok(contents.match(/^Content-type: message\/delivery-status/m), 'its a bounce report'); + assert.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'bounce report contains final recipient'); + assert.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); + assert.ok(contents.match(/^Status: 5\.0\.0/m), 'bounce report contains status field with ext. smtp code'); + assert.ok(contents.match(/Absolutely not acceptable\. Basic Test Only\./), 'original upstream message available'); outbound_context.exports.send_email = orig_send_email; - - test.done(); - }; + done() + } // The playbook // from remote: This line is to be sent (from an mocked remote SMTP) to haraka outbound. This is done in this test. @@ -105,27 +99,27 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => { + }) + }) + }) - }); - }); - }, - 'test that early response of 3XX triggers temp_fail': test => { - test.expect(7); + it('test that early response of 3XX triggers temp_fail', (done) => { - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'early-3XX: outbound.temp_fail called'); - test.equal('3.0.0', this.todo.rcpt_to[0].dsn_status, 'early-3XX: dsn status = 3.0.0'); - test.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'early-3XX: dsn action = delayed'); - test.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/No time for you right now/), 'early-3XX: original upstream message available'); + assert.ok(true, 'early-3XX: outbound.temp_fail called'); + assert.equal('3.0.0', this.todo.rcpt_to[0].dsn_status, 'early-3XX: dsn status = 3.0.0'); + assert.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'early-3XX: dsn action = delayed'); + assert.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/No time for you right now/), 'early-3XX: original upstream message available'); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); - }; + done() + } + const testPlaybook = [ { 'from': 'remote', 'line': '220 testing-smtp' }, @@ -139,26 +133,26 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => { + + }) + }) + }) - }); - }); - }, - 'test that response of 4XX for RCPT-TO triggers temp_fail': test => { - test.expect(8); + it('test that response of 4XX for RCPT-TO triggers temp_fail', (done) => { - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'RCPT-TO-4XX: outbound.temp_fail called'); - test.equal('4.0.0', this.todo.rcpt_to[0].dsn_status, 'RCPT-TO-4XX: dsn status = 4.0.0'); - test.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'RCPT-TO-4XX: dsn action = delayed'); - test.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/Currently not available\. Try again later\./), 'RCPT-TO-4XX: original upstream message available'); + assert.ok(true, 'RCPT-TO-4XX: outbound.temp_fail called'); + assert.equal('4.0.0', this.todo.rcpt_to[0].dsn_status, 'RCPT-TO-4XX: dsn status = 4.0.0'); + assert.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'RCPT-TO-4XX: dsn action = delayed'); + assert.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/Currently not available\. Try again later\./), 'RCPT-TO-4XX: original upstream message available'); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); + done() }; const testPlaybook = [ { 'from': 'remote', 'line': '220 testing-smtp' }, @@ -176,26 +170,24 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => {}) + }) + }) - }); - }); - }, - 'test that response of 4XX for DATA triggers temp_fail': test => { - test.expect(9); + it('test that response of 4XX for DATA triggers temp_fail', (done) => { - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_temp_fail = HMailItem.prototype.temp_fail; HMailItem.prototype.temp_fail = function (err, opts) { - test.ok(true, 'DATA-4XX: outbound.temp_fail called'); - test.equal('4.6.0', this.todo.rcpt_to[0].dsn_status, 'DATA-4XX: dsn status = 4.6.0'); - test.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'DATA-4XX: dsn action = delayed'); - test.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/Currently I do not like ascii art cats\./), 'DATA-4XX: original upstream message available'); + assert.ok(true, 'DATA-4XX: outbound.temp_fail called'); + assert.equal('4.6.0', this.todo.rcpt_to[0].dsn_status, 'DATA-4XX: dsn status = 4.6.0'); + assert.equal('delayed', this.todo.rcpt_to[0].dsn_action, 'DATA-4XX: dsn action = delayed'); + assert.ok(this.todo.rcpt_to[0].dsn_smtp_response.match(/Currently I do not like ascii art cats\./), 'DATA-4XX: original upstream message available'); HMailItem.prototype.temp_fail = orig_temp_fail; - test.done(); + done() }; const testPlaybook = [ { 'from': 'remote', 'line': '220 testing-smtp' }, @@ -217,28 +209,28 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => { + + }) + }) + }) - }); - }); - }, - 'test that response of 5XX for RCPT-TO triggers send_email() containing bounce msg with codes and message': test => { - test.expect(10); + it('test that response of 5XX for RCPT-TO triggers send_email() containing bounce msg with codes and message', (done) => { - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_send_email = outbound_context.exports.send_email; outbound_context.exports.send_email = (from, to, contents, cb, opts) => { - test.ok(true, 'RCPT-TO-5XX: outbound.send_email called'); - test.ok(contents.match(/^Content-type: message\/delivery-status/m), 'RCPT-TO-5XX: its a bounce report'); - test.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'RCPT-TO-5XX: bounce report contains final recipient'); - test.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); - test.ok(contents.match(/^Status: 5\.1\.1/m), 'RCPT-TO-5XX: bounce report contains status field with our ext. smtp code'); - test.ok(contents.match(/Not available and will not come back/), 'RCPT-TO-5XX: original upstream message available'); + assert.ok(true, 'RCPT-TO-5XX: outbound.send_email called'); + assert.ok(contents.match(/^Content-type: message\/delivery-status/m), 'RCPT-TO-5XX: its a bounce report'); + assert.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'RCPT-TO-5XX: bounce report contains final recipient'); + assert.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); + assert.ok(contents.match(/^Status: 5\.1\.1/m), 'RCPT-TO-5XX: bounce report contains status field with our ext. smtp code'); + assert.ok(contents.match(/Not available and will not come back/), 'RCPT-TO-5XX: original upstream message available'); outbound_context.exports.send_email = orig_send_email; - test.done(); + done() }; const testPlaybook = [ { 'from': 'remote', 'line': '220 testing-smtp' }, @@ -256,28 +248,26 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => {}) + }) + }) - }); - }); - }, - 'test that response of 5XX for DATA triggers send_email() containing bounce msg with codes and message': test => { - test.expect(11); + it('test that response of 5XX for DATA triggers send_email() containing bounce msg with codes and message', (done) => { - util_hmailitem.newMockHMailItem(outbound_context, test, {}, mock_hmail => { + util_hmailitem.newMockHMailItem(outbound_context, done, {}, mock_hmail => { const mock_socket = mock_sock.connect('testhost', 'testport'); mock_socket.writable = true; const orig_send_email = outbound_context.exports.send_email; outbound_context.exports.send_email = (from, to, contents, cb, opts) => { - test.ok(true, 'DATA-5XX: outbound.send_email called'); - test.ok(contents.match(/^Content-type: message\/delivery-status/m), 'DATA-5XX: its a bounce report'); - test.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'DATA-5XX: bounce report contains final recipient'); - test.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); - test.ok(contents.match(/^Status: 5\.6\.0/m), 'DATA-5XX: bounce report contains status field with our ext. smtp code'); - test.ok(contents.match(/I never did and will like ascii art cats/), 'DATA-5XX: original upstream message available'); + assert.ok(true, 'DATA-5XX: outbound.send_email called'); + assert.ok(contents.match(/^Content-type: message\/delivery-status/m), 'DATA-5XX: its a bounce report'); + assert.ok(contents.match(/^Final-Recipient: rfc822;recipient@domain/m), 'DATA-5XX: bounce report contains final recipient'); + assert.ok(contents.match(/^Action: failed/m), 'DATA-5XX: bounce report contains action field'); + assert.ok(contents.match(/^Status: 5\.6\.0/m), 'DATA-5XX: bounce report contains status field with our ext. smtp code'); + assert.ok(contents.match(/I never did and will like ascii art cats/), 'DATA-5XX: original upstream message available'); outbound_context.exports.send_email = orig_send_email; - test.done(); + done() }; const testPlaybook = [ { 'from': 'remote', 'line': '220 testing-smtp' }, @@ -299,10 +289,8 @@ exports.bounce_3464 = { { 'from': 'haraka', 'test': 'QUIT', end_test: true }, // this will trigger calling the callback ]; - util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, test, testPlaybook, () => { - - }); - }); - }, -} + util_hmailitem.playTestSmtpConversation(mock_hmail, mock_socket, done, testPlaybook, () => {}) + }) + }) +}) diff --git a/tests/plugins.js b/tests/plugins.js index 9a823d407..af509cf13 100644 --- a/tests/plugins.js +++ b/tests/plugins.js @@ -2,8 +2,10 @@ process.env.WITHOUT_CONFIG_CACHE=true; -const fs = require('fs'); -const path = require('path'); +const assert = require('node:assert') +const fs = require('node:fs'); +const path = require('node:path'); + const plugin = require('../plugins'); const piName = 'testPlugin'; @@ -39,209 +41,188 @@ From: https://github.com/haraka/Haraka/pull/1278#issuecomment-172134064 */ -exports.plugin = { - 'new Plugin() object': test => { +describe('plugin', () => { + + it('new Plugin() object', () => { const pi = new plugin.Plugin(piName); - test.expect(1); - test.ok(pi); - test.done(); - } -} - -const toPath = path.join('config', `${piName}.timeout`); - -exports.get_timeout = { - tearDown : done => { - fs.unlink(toPath, done); - }, - '0s' (test) { - test.expect(1); - fs.writeFile(toPath, '0', () => { - this.plugin = new plugin.Plugin(piName); - test.equal( this.plugin.timeout, 0 ); - test.done(); - }) - }, - '3s' (test) { - test.expect(1); - fs.writeFile(toPath, '3', () => { - this.plugin = new plugin.Plugin(piName); - test.equal( this.plugin.timeout, 3 ); - test.done(); - }) - }, - '60s' (test) { - test.expect(1); - fs.writeFile(toPath, '60', () => { - this.plugin = new plugin.Plugin(piName); - test.equal( this.plugin.timeout, 60 ); - test.done(); - }) - }, - '30s default (overrides NaN)' (test) { - test.expect(1); - fs.writeFile(toPath, 'apple', () => { - this.plugin = new plugin.Plugin(piName); - test.equal( this.plugin.timeout, 30 ); - test.done(); - }) - }, -} - -exports.plugin_paths = { - setUp : done => { - delete process.env.HARAKA; - done(); - }, - tearDown : done => { - delete process.env.HARAKA; - done(); - }, - 'CORE plugin: (tls)' : test => { - const p = new plugin.Plugin('tls'); - - test.expect(1); - test.equal(p.plugin_path, path.resolve(__dirname, '..', 'plugins', 'tls.js')); - test.done(); - }, - - 'INSTALLED override: (tls)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('tls'); - - test.expect(1); - test.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'tls.js')); - test.done(); - }, - - 'INSTALLED node_modules package plugin: (test-plugin)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('test-plugin'); - - test.expect(3); - test.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'node_modules', 'test-plugin', 'package.json')); - test.ok(p.hasPackageJson); - try { - p._compile(); - test.ok(true, "compiles OK"); - } - catch (e) { - console.error(e.stack); - test.ok(false, "compiles OK"); - } - test.done(); - }, - - 'CORE package plugin: asn': test => { - const p = new plugin.Plugin('haraka-plugin-asn'); - - test.expect(2); - test.equal(p.plugin_path, path.resolve(__dirname, '..', 'node_modules', 'haraka-plugin-asn', 'package.json')); - test.ok(p.hasPackageJson); - test.done(); - }, - - 'plugins overrides node_modules': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('load_first'); - - test.expect(3); - test.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'load_first.js')); - try { - p._compile(); - test.ok(true, "compiles OK"); - } - catch (e) { - console.error(e.stack); - test.ok(false, "compiles OK"); - } - test.ok(p.loaded_first); - test.done(); - }, - - 'INSTALLED plugins folder plugin: (folder_plugin)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('folder_plugin'); - - test.expect(3); - test.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'folder_plugin', 'package.json')); - test.ok(p.hasPackageJson); - try { - p._compile(); - test.ok(true, "compiles OK"); - } - catch (e) { - console.error(e.stack); - test.ok(false, "compiles OK"); - } - test.done(); - }, - - 'Inheritance: (inherits)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('inherits'); - - test.expect(3); - test.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'inherits.js')); - try { - p._compile(); - test.ok(true, "compiles OK"); - } - catch (e) { - console.error(e.stack); - test.ok(false, "compiles OK"); - } - p.register(); - test.ok(p.base.base_plugin); - test.done(); - }, -} - -exports.plugin_config = { - setUp : done => { - delete process.env.HARAKA; - done(); - }, - tearDown : done => { - delete process.env.HARAKA; - done(); - }, - 'CORE plugin: (tls)' : test => { - - const p = new plugin.Plugin('tls'); - - test.expect(2); - test.equal(p.config.root_path, path.resolve(__dirname, '..', 'config')); - test.equal(p.config.overrides_path, undefined); - test.done(); - }, - - 'INSTALLED override: (tls)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('tls'); - - test.expect(3); - test.equal(p.config.root_path, path.resolve(__dirname, '..', 'config')); - test.equal(p.config.overrides_path, path.resolve(__dirname, 'installation', 'config')); - const tls_ini = p.config.get('tls.ini'); - test.equal(tls_ini.main.ciphers, 'test'); - test.done(); - }, - - 'INSTALLED node_modules package plugin: (test-plugin)': test => { - process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); - - const p = new plugin.Plugin('test-plugin'); - - test.expect(2); - test.equal(p.config.root_path, path.resolve(__dirname, 'installation', 'node_modules', 'test-plugin', 'config')); - test.equal(p.config.overrides_path, path.resolve(__dirname, 'installation', 'config')); - test.done(); - }, -} + assert.ok(pi); + }) + + describe('get_timeout', () => { + const toPath = path.resolve('config', `${piName}.timeout`); + it('0s', (done) => { + fs.writeFile(toPath, '0', () => { + this.plugin = new plugin.Plugin(piName); + assert.equal(this.plugin.timeout, 0); + fs.unlink(toPath, done); + }) + }) + + it('3s', (done) => { + fs.writeFile(toPath, '3', () => { + this.plugin = new plugin.Plugin(piName); + assert.equal(this.plugin.timeout, 3); + fs.unlink(toPath, done); + }) + }) + + it('60s', (done) => { + fs.writeFile(toPath, '60', () => { + this.plugin = new plugin.Plugin(piName); + assert.equal(this.plugin.timeout, 60); + fs.unlink(toPath, done); + }) + }) + + it('30s default (overrides NaN)', (done) => { + fs.writeFile(toPath, 'apple', () => { + this.plugin = new plugin.Plugin(piName); + assert.equal(this.plugin.timeout, 30); + fs.unlink(toPath, done); + }) + }) + }) + + describe('plugin_paths', () => { + beforeEach((done) => { + delete process.env.HARAKA; + done() + }) + + afterEach((done) => { + delete process.env.HARAKA; + done() + }) + + it('CORE plugin: (tls)', () => { + const p = new plugin.Plugin('tls'); + + assert.equal(p.plugin_path, path.resolve(__dirname, '..', 'plugins', 'tls.js')); + }) + + it('INSTALLED override: (tls)', () => { + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('tls'); + + assert.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'tls.js')); + }) + + it('INSTALLED node_modules package plugin: (test-plugin)', () => { + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('test-plugin'); + + assert.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'node_modules', 'test-plugin', 'package.json')); + assert.ok(p.hasPackageJson); + try { + p._compile(); + assert.ok(true, "compiles OK"); + } + catch (e) { + console.error(e.stack); + assert.ok(false, "compiles OK"); + } + }) + + it('CORE package plugin: spf', () => { + const p = new plugin.Plugin('haraka-plugin-spf'); + + assert.equal(p.plugin_path, path.resolve(__dirname, '..', 'node_modules', 'haraka-plugin-spf', 'package.json')); + assert.ok(p.hasPackageJson); + }) + + it('plugins overrides node_modules', () => { + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('load_first'); + + assert.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'load_first.js')); + try { + p._compile(); + assert.ok(true, "compiles OK"); + } + catch (e) { + console.error(e.stack); + assert.ok(false, "compiles OK"); + } + assert.ok(p.loaded_first); + }) + + it('INSTALLED plugins folder plugin: (folder_plugin)', () => { + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('folder_plugin'); + + assert.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'folder_plugin', 'package.json')); + assert.ok(p.hasPackageJson); + try { + p._compile(); + assert.ok(true, "compiles OK"); + } + catch (e) { + console.error(e.stack); + assert.ok(false, "compiles OK"); + } + }) + + it('Inheritance: (inherits)', () => { + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('inherits'); + + assert.equal(p.plugin_path, path.resolve(__dirname, 'installation', 'plugins', 'inherits.js')); + try { + p._compile(); + assert.ok(true, "compiles OK"); + } + catch (e) { + console.error(e.stack); + assert.ok(false, "compiles OK"); + } + p.register(); + assert.ok(p.base.base_plugin); + }) + }) + + describe('plugin_config', () => { + beforeEach((done) => { + delete process.env.HARAKA; + done(); + }) + + afterEach((done) => { + delete process.env.HARAKA; + done(); + }) + + it('CORE plugin: (tls)', () => { + const p = new plugin.Plugin('tls'); + + assert.equal(p.config.root_path, path.resolve(__dirname, '..', 'config')); + assert.equal(p.config.overrides_path, undefined); + }) + + it('INSTALLED override: (tls)', () => { + + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('tls'); + + assert.equal(p.config.root_path, path.resolve(__dirname, '..', 'config')); + assert.equal(p.config.overrides_path, path.resolve(__dirname, 'installation', 'config')); + const tls_ini = p.config.get('tls.ini'); + assert.equal(tls_ini.main.ciphers, 'test'); + }) + + it('INSTALLED node_modules package plugin: (test-plugin)', () => { + + process.env.HARAKA = path.resolve(__dirname, '..', 'tests', 'installation'); + + const p = new plugin.Plugin('test-plugin'); + + assert.equal(p.config.root_path, path.resolve(__dirname, 'installation', 'node_modules', 'test-plugin', 'config')); + assert.equal(p.config.overrides_path, path.resolve(__dirname, 'installation', 'config')); + }) + }) +}) diff --git a/tests/plugins/auth/auth_base.js b/tests/plugins/auth/auth_base.js index de205e3e9..cffec1568 100644 --- a/tests/plugins/auth/auth_base.js +++ b/tests/plugins/auth/auth_base.js @@ -1,10 +1,11 @@ 'use strict'; +const assert = require('node:assert') const { Address } = require('address-rfc2821'); -const fixtures = require('haraka-test-fixtures'); -const utils = require('haraka-utils'); +const fixtures = require('haraka-test-fixtures'); +const utils = require('haraka-utils'); -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('auth/auth_base'); @@ -19,7 +20,7 @@ function _set_up (done) { done(); } -function _set_up_2 (done) { +const _set_up_2 = (done) => { this.plugin = new fixtures.plugin('auth/auth_base'); @@ -35,7 +36,7 @@ function _set_up_2 (done) { done(); } -function _set_up_custom_pwcb_opts (done) { +const _set_up_custom_pwcb_opts = (done) => { this.plugin = new fixtures.plugin('auth/auth_base'); this.plugin.check_plain_passwd = (connection, user, passwd, pwok_cb) => { @@ -61,445 +62,423 @@ function _set_up_custom_pwcb_opts (done) { done(); } -exports.hook_capabilities = { - setUp : _set_up, - 'no TLS, no auth' (test) { - this.plugin.hook_capabilities((rc, msg) => { - test.expect(3); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.equal(null, this.connection.capabilities); - test.done(); - }, this.connection); - }, - 'with TLS, auth is offered' (test) { - this.connection.tls.enabled=true; - this.connection.capabilities=[]; - this.plugin.hook_capabilities((rc, msg) => { - test.expect(4); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.capabilities.length); - test.ok(this.connection.capabilities[0] === 'AUTH PLAIN LOGIN CRAM-MD5'); - // console.log(this.connection.capabilities); - test.done(); - }, this.connection); - }, -} - -exports.get_plain_passwd = { - setUp : _set_up, - 'get_plain_passwd, no result' (test) { - this.plugin.get_plain_passwd('user', pass => { - test.expect(1); - test.equal(pass, null); - test.done(); - }); - }, - 'get_plain_passwd, test user' (test) { - this.plugin.get_plain_passwd('test', pass => { - test.expect(1); - test.equal(pass, 'testpass'); - test.done(); - }); - }, -} - -exports.check_plain_passwd = { - setUp : _set_up, - 'valid password' (test) { - this.plugin.check_plain_passwd(this.connection, 'test', 'testpass', pass => { - test.expect(1); - test.equal(pass, true); - test.done(); - }); - }, - 'wrong password' (test) { - this.plugin.check_plain_passwd(this.connection, 'test', 'test1pass', pass => { - test.expect(1); - test.equal(pass, false); - test.done(); - }); - }, - 'null password' (test) { - this.plugin.check_plain_passwd(this.connection, 'test', null, pass => { - test.expect(1); - test.equal(pass, false); - test.done(); - }); - }, -} - -exports.select_auth_method = { - setUp : _set_up, - 'no auth methods yield no result' (test) { - this.plugin.select_auth_method((code) => { - test.equal(code, null); - test.equal(false, this.connection.relaying); - test.done(); - }, this.connection, 'AUTH PLAIN'); - }, - 'invalid AUTH method, no result' (test) { - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN','CRAM-MD5']; - this.plugin.select_auth_method((code) => { - test.expect(2); - test.equal(code, null); - test.equal(false, this.connection.relaying); - test.done(); - }, this.connection, 'AUTH FOO'); - }, - 'valid AUTH method, valid attempt' (test) { - const method = `PLAIN ${utils.base64('discard\0test\0testpass')}`; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.select_auth_method((code) => { - test.expect(2); - test.equal(code, OK); - test.ok(this.connection.relaying); - test.done(); - }, this.connection, method); - }, -} - -exports.auth_plain = { - setUp : _set_up, - 'params type=string returns OK' (test) { - test.expect(2); - const next = function () { - test.equal(arguments[0], OK); - test.equal(false, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.auth_plain(next, this.connection, 'AUTH FOO'); - }, - 'params type=empty array, returns OK' (test) { - const next = function () { - test.expect(2); - test.equal(arguments[0], OK); - test.equal(false, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.auth_plain(next, this.connection, []); - }, - 'params type=array, successful auth' (test) { - test.expect(2); - const method = utils.base64('discard\0test\0testpass'); - const next = function () { - test.equal(arguments[0], OK); - test.ok(this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.auth_plain(next, this.connection, [method]); - }, - 'params type=with two line login' (test) { - const next = function () { - test.expect(2); - test.equal(this.connection.notes.auth_plain_asked_login, true); - test.equal(arguments[0], OK); - test.done(); - }.bind(this); - this.plugin.auth_plain(next, this.connection, ''); - }, -} - -exports.check_user = { - setUp : _set_up_2, - 'bad auth' (test) { - const credentials = ['matt','ttam']; - this.plugin.check_user((code) => { - test.expect(3); - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.equal(this.connection.notes.auth_custom_note, 'custom_note'); - test.done(); - }, this.connection, credentials, 'PLAIN'); - }, - 'good auth' (test) { - const credentials = ['test','testpass']; - this.plugin.check_user((code) => { - test.expect(3); - test.equal(code, OK); - test.ok(this.connection.relaying); - test.equal(this.connection.notes.auth_custom_note, 'custom_note'); - test.done(); - }, this.connection, credentials, 'PLAIN'); - }, -} - -exports.check_user_custom_opts = { - setUp: _set_up_custom_pwcb_opts, - 'legacyok_nomessage' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.deepEqual(this.connection.notes.resp_strings, [[ 235, '2.7.0 Authentication successful' ]]); - test.done(); - }, this.connection, ['legacyok_nomessage', 'any'], 'PLAIN'); - }, - 'legacyfail_nomessage' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.deepEqual(this.connection.notes.resp_strings, [ [ 535, '5.7.8 Authentication failed' ] ]); - test.done(); - }, this.connection, ['legacyfail_nomessage', 'any'], 'PLAIN'); - }, - 'legacyok_message' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.deepEqual(this.connection.notes.resp_strings, [[ 235, 'GREAT SUCCESS' ]]); - test.done(); - }, this.connection, ['legacyok_message', 'any'], 'PLAIN'); - }, - 'legacyfail_message' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.deepEqual(this.connection.notes.resp_strings, [[ 535, 'FAIL 123' ]]); - test.done(); - }, this.connection, ['legacyfail_message', 'any'], 'PLAIN'); - }, - 'newok' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.deepEqual(this.connection.notes.resp_strings, [[ 215, 'KOKOKO' ]]); - test.done(); - }, this.connection, ['newok', 'any'], 'PLAIN'); - }, - 'newfail' (test) { - this.plugin.check_user((code, msg) => { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.deepEqual(this.connection.notes.resp_strings, [[ 555, 'OHOHOH' ]]); - test.done(); - }, this.connection, ['newfail', 'any'], 'PLAIN'); - }, -} - -exports.auth_notes_are_set = { - setUp : _set_up_2, - 'bad auth: no notes should be set' (test) { - const credentials = ['matt','ttam']; - this.plugin.check_user((code) => { - test.equal(this.connection.notes.auth_user, undefined); - test.equal(this.connection.notes.auth_passwd, undefined); - test.done(); - }, this.connection, credentials, 'PLAIN'); - }, - 'good auth: dont store password' (test) { - const creds = ['test','testpass']; - this.plugin.blankout_password = true; - this.plugin.check_user((code) => { - test.equal(this.connection.notes.auth_user, creds[0]); - test.equal(this.connection.notes.auth_passwd, undefined); - test.done(); - }, this.connection, creds, 'PLAIN'); - }, - 'good auth: store password (default)' (test) { - const creds = ['test','testpass']; - this.plugin.check_user((code) => { - test.equal(this.connection.notes.auth_user, creds[0]); - test.equal(this.connection.notes.auth_passwd, creds[1]); - test.done(); - }, this.connection, creds, 'PLAIN'); - }, -} - -exports.hook_unrecognized_command = { - setUp : _set_up, - 'AUTH type FOO' (test) { - const params = ['AUTH','FOO']; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command((code) => { - test.expect(2); - test.equal(code, null); - test.equal(this.connection.relaying, false); - test.done(); - }, this.connection, params); - }, - 'AUTH PLAIN' (test) { - const params = ['AUTH','PLAIN', utils.base64('discard\0test\0testpass')]; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command((code) => { - test.expect(2); - test.equal(code, OK); - test.ok(this.connection.relaying); - test.done(); - }, this.connection, params); - }, - 'AUTH PLAIN, authenticating' (test) { - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.connection.notes.authenticating=true; - this.connection.notes.auth_method='PLAIN'; - this.plugin.hook_unrecognized_command((code) => { - test.expect(2); - test.equal(code, OK); - test.ok(this.connection.relaying); - test.done(); - }, this.connection, [utils.base64('discard\0test\0testpass')]); - } -} - -exports.auth_login = { - setUp : _set_up, - 'AUTH LOGIN' (test) { - test.expect(8); - - const next3 = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.done(); - }.bind(this); - - const next2 = function (code) { - test.equal(code, OK); - test.equal(this.connection.notes.auth_login_userlogin, 'test'); - test.equal(this.connection.relaying, false); - this.plugin.hook_unrecognized_command(next3, this.connection, [utils.base64('testpass')]); - }.bind(this); - - const next = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.equal(this.connection.notes.auth_login_asked_login , true); - - this.plugin.hook_unrecognized_command(next2, this.connection, [utils.base64('test')]); - }.bind(this); - - const params = ['AUTH','LOGIN']; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command(next, this.connection, params); - }, - - 'AUTH LOGIN ' (test) { - test.expect(6); - - const next2 = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.done(); - }.bind(this); - - const next = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.equal(this.connection.notes.auth_login_userlogin, 'test'); - test.equal(this.connection.notes.auth_login_asked_login , true); - - this.plugin.hook_unrecognized_command(next2, this.connection, [utils.base64('testpass')]); - }.bind(this); - - const params = ['AUTH','LOGIN', utils.base64('test')]; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command(next, this.connection, params); - }, - - 'AUTH LOGIN , bad protocol' (test) { - test.expect(7); - - const next2 = function (code, msg) { - test.equal(code, DENYDISCONNECT); - test.equal(msg, 'bad protocol'); - test.equal(this.connection.relaying, false); - test.done(); - }.bind(this); - - const next = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.equal(this.connection.notes.auth_login_userlogin, 'test'); - test.equal(this.connection.notes.auth_login_asked_login , true); - - this.plugin.hook_unrecognized_command(next2, this.connection, ['AUTH', 'LOGIN']); - }.bind(this); - - const params = ['AUTH','LOGIN', utils.base64('test')]; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command(next, this.connection, params); - }, - - - 'AUTH LOGIN, reauthentication' (test) { - test.expect(9); - - function next3 (code) { - test.equal(code, OK); - - test.done(); - } - - const next2 = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, true); - test.equal(this.connection.notes.auth_login_userlogin, null); - test.equal(this.connection.notes.auth_login_asked_login , false); - - this.plugin.hook_unrecognized_command(next3, this.connection, ['AUTH','LOGIN']); - }.bind(this); - - const next = function (code) { - test.equal(code, OK); - test.equal(this.connection.relaying, false); - test.equal(this.connection.notes.auth_login_userlogin, 'test'); - test.equal(this.connection.notes.auth_login_asked_login , true); - - this.plugin.hook_unrecognized_command(next2, this.connection, [utils.base64('testpass')]); - }.bind(this); - - const params = ['AUTH','LOGIN', utils.base64('test')]; - this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; - this.plugin.hook_unrecognized_command(next, this.connection, params); - } -} - -exports.hexi = { - setUp : _set_up, - 'hexi' (test) { - test.expect(2); - test.equal(this.plugin.hexi(512), 200); - test.equal(this.plugin.hexi(8), 8); - test.done(); - }, -} - -exports.constrain_sender = { - setUp : _set_up, - 'constrain_sender, domain match' (test) { - this.mfrom = new Address('user@example.com') - this.connection.results.add({name: 'auth'}, { user: 'user@example.com' }) - test.expect(1); - this.plugin.constrain_sender((resCode) => { - test.equal(resCode, undefined) - test.done(); - }, - this.connection, - [this.mfrom], - ) - }, - 'constrain_sender, domain mismatch' (test) { - this.mfrom = new Address('user@example.net') - this.connection.results.add({name: 'auth'}, { user: 'user@example.com' }) - test.expect(2); - this.plugin.constrain_sender((resCode, denyMsg) => { - test.equal(resCode, DENY) - test.ok(denyMsg) - test.done(); - }, - this.connection, - [this.mfrom], - ) - }, - 'constrain_sender, no domain' (test) { - this.mfrom = new Address('user@example.com') - this.connection.results.add({name: 'auth'}, { user: 'user' }) - test.expect(1); - this.plugin.constrain_sender((resCode) => { - test.equal(resCode, undefined) - test.done(); - }, - this.connection, - [this.mfrom], - ) - }, -} +describe('auth_base', () => { + + describe('hook_capabilities', () => { + beforeEach(_set_up) + + it('no TLS, no auth', (done) => { + this.plugin.hook_capabilities((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.equal(null, this.connection.capabilities); + done(); + }, this.connection); + }) + + it('with TLS, auth is offered', (done) => { + this.connection.tls.enabled=true; + this.connection.capabilities=[]; + this.plugin.hook_capabilities((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.capabilities.length); + assert.ok(this.connection.capabilities[0] === 'AUTH PLAIN LOGIN CRAM-MD5'); + // console.log(this.connection.capabilities); + done(); + }, this.connection); + }) + }) + + describe('get_plain_passwd', () => { + beforeEach(_set_up) + + it('get_plain_passwd, no result', (done) => { + this.plugin.get_plain_passwd('user', pass => { + assert.equal(pass, null); + done(); + }); + }) + it('get_plain_passwd, test user', (done) => { + this.plugin.get_plain_passwd('test', pass => { + assert.equal(pass, 'testpass'); + done(); + }); + }) + }) + + describe('check_plain_passwd', () => { + beforeEach(_set_up) + + it('valid password', (done) => { + this.plugin.check_plain_passwd(this.connection, 'test', 'testpass', pass => { + assert.equal(pass, true); + done(); + }); + }) + + it('wrong password', (done) => { + this.plugin.check_plain_passwd(this.connection, 'test', 'test1pass', pass => { + assert.equal(pass, false); + done(); + }); + }) + + it('null password', (done) => { + this.plugin.check_plain_passwd(this.connection, 'test', null, pass => { + assert.equal(pass, false); + done(); + }); + }) + }) + + describe('select_auth_method', () => { + beforeEach(_set_up) + + it('no auth methods yield no result', (done) => { + this.plugin.select_auth_method((code) => { + assert.equal(code, null); + assert.equal(false, this.connection.relaying); + done(); + }, this.connection, 'AUTH PLAIN'); + }) + + it('invalid AUTH method, no result', (done) => { + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN','CRAM-MD5']; + this.plugin.select_auth_method((code) => { + assert.equal(code, null); + assert.equal(false, this.connection.relaying); + done(); + }, this.connection, 'AUTH FOO'); + }) + + it('valid AUTH method, valid attempt', (done) => { + const method = `PLAIN ${utils.base64('discard\0test\0testpass')}`; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.select_auth_method((code) => { + assert.equal(code, OK); + assert.ok(this.connection.relaying); + done(); + }, this.connection, method); + }) + }) + + describe('auth_plain', () => { + beforeEach(_set_up) + + it('params type=string returns OK', (done) => { + this.plugin.auth_plain((rc) => { + assert.equal(rc, OK); + assert.equal(false, this.connection.relaying); + done(); + }, this.connection, 'AUTH FOO'); + }) + + it('params type=empty array, returns OK', (done) => { + this.plugin.auth_plain((rc) => { + assert.equal(rc, OK); + assert.equal(false, this.connection.relaying); + done(); + }, this.connection, []); + }) + + it('params type=array, successful auth', (done) => { + const method = utils.base64('discard\0test\0testpass'); + this.plugin.auth_plain((rc) => { + assert.equal(rc, OK); + assert.ok(this.connection.relaying); + done(); + }, this.connection, [method]); + }) + + it('params type=with two line login', (done) => { + this.plugin.auth_plain((rc) => { + assert.equal(this.connection.notes.auth_plain_asked_login, true); + assert.equal(rc, OK); + done(); + }, this.connection, ''); + }) + }) + + describe('check_user', () => { + beforeEach(_set_up_2) + + it('bad auth', (done) => { + const credentials = ['matt','ttam']; + this.plugin.check_user((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.equal(this.connection.notes.auth_custom_note, 'custom_note'); + done(); + }, this.connection, credentials, 'PLAIN'); + }) + + it('good auth', (done) => { + const credentials = ['test','testpass']; + this.plugin.check_user((code) => { + assert.equal(code, OK); + assert.ok(this.connection.relaying); + assert.equal(this.connection.notes.auth_custom_note, 'custom_note'); + done(); + }, this.connection, credentials, 'PLAIN'); + }) + }) + + describe('check_user_custom_opts', () => { + beforeEach(_set_up_custom_pwcb_opts) + + it('legacyok_nomessage', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, true); + assert.deepEqual(this.connection.notes.resp_strings, [[ 235, '2.7.0 Authentication successful' ]]); + done(); + }, this.connection, ['legacyok_nomessage', 'any'], 'PLAIN'); + }) + + it('legacyfail_nomessage', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.deepEqual(this.connection.notes.resp_strings, [ [ 535, '5.7.8 Authentication failed' ] ]); + done(); + }, this.connection, ['legacyfail_nomessage', 'any'], 'PLAIN'); + }) + + it('legacyok_message', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, true); + assert.deepEqual(this.connection.notes.resp_strings, [[ 235, 'GREAT SUCCESS' ]]); + done(); + }, this.connection, ['legacyok_message', 'any'], 'PLAIN'); + }) + + it('legacyfail_message', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.deepEqual(this.connection.notes.resp_strings, [[ 535, 'FAIL 123' ]]); + done(); + }, this.connection, ['legacyfail_message', 'any'], 'PLAIN'); + }) + + it('newok', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, true); + assert.deepEqual(this.connection.notes.resp_strings, [[ 215, 'KOKOKO' ]]); + done(); + }, this.connection, ['newok', 'any'], 'PLAIN'); + }) + + it('newfail', (done) => { + this.plugin.check_user((code, msg) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.deepEqual(this.connection.notes.resp_strings, [[ 555, 'OHOHOH' ]]); + done(); + }, this.connection, ['newfail', 'any'], 'PLAIN'); + }) + }) + + describe('auth_notes_are_set', () => { + beforeEach(_set_up_2) + + it('bad auth: no notes should be set', (done) => { + const credentials = ['matt','ttam']; + this.plugin.check_user((code) => { + assert.equal(this.connection.notes.auth_user, undefined); + assert.equal(this.connection.notes.auth_passwd, undefined); + done(); + }, this.connection, credentials, 'PLAIN'); + }) + + it('good auth: dont store password', (done) => { + const creds = ['test','testpass']; + this.plugin.blankout_password = true; + this.plugin.check_user((code) => { + assert.equal(this.connection.notes.auth_user, creds[0]); + assert.equal(this.connection.notes.auth_passwd, undefined); + done(); + }, this.connection, creds, 'PLAIN'); + }) + + it('good auth: store password (default)', (done) => { + const creds = ['test','testpass']; + this.plugin.check_user((code) => { + assert.equal(this.connection.notes.auth_user, creds[0]); + assert.equal(this.connection.notes.auth_passwd, creds[1]); + done(); + }, this.connection, creds, 'PLAIN'); + }) + }) + + describe('hook_unrecognized_command', () => { + beforeEach(_set_up) + + it('AUTH type FOO', (done) => { + const params = ['AUTH','FOO']; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, null); + assert.equal(this.connection.relaying, false); + done(); + }, this.connection, params); + }) + + it('AUTH PLAIN', (done) => { + const params = ['AUTH','PLAIN', utils.base64('discard\0test\0testpass')]; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.ok(this.connection.relaying); + done(); + }, this.connection, params); + }) + + it('AUTH PLAIN, authenticating', (done) => { + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.connection.notes.authenticating=true; + this.connection.notes.auth_method='PLAIN'; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.ok(this.connection.relaying); + done(); + }, this.connection, [utils.base64('discard\0test\0testpass')]); + }) + }) + + describe('auth_login', () => { + beforeEach(_set_up) + + it('AUTH LOGIN', (done) => { + const params = ['AUTH','LOGIN']; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.equal(this.connection.notes.auth_login_asked_login , true); + + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.notes.auth_login_userlogin, 'test'); + assert.equal(this.connection.relaying, false); + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, true); + done(); + }, this.connection, [utils.base64('testpass')]); + }, this.connection, [utils.base64('test')]); + }, this.connection, params); + }) + + it('AUTH LOGIN ', (done) => { + const params = ['AUTH','LOGIN', utils.base64('test')]; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.equal(this.connection.notes.auth_login_userlogin, 'test'); + assert.equal(this.connection.notes.auth_login_asked_login , true); + + this.plugin.hook_unrecognized_command((code2) => { + assert.equal(code2, OK); + assert.equal(this.connection.relaying, true); + done(); + }, this.connection, [utils.base64('testpass')]); + }, this.connection, params); + }) + + it('AUTH LOGIN , bad protocol', (done) => { + + const params = ['AUTH','LOGIN', utils.base64('test')]; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.equal(this.connection.notes.auth_login_userlogin, 'test'); + assert.equal(this.connection.notes.auth_login_asked_login , true); + + this.plugin.hook_unrecognized_command((code, msg) => { + assert.equal(code, DENYDISCONNECT); + assert.equal(msg, 'bad protocol'); + assert.equal(this.connection.relaying, false); + done(); + }, this.connection, ['AUTH', 'LOGIN']); + }, this.connection, params); + }) + + it('AUTH LOGIN, reauthentication', (done) => { + const params = ['AUTH','LOGIN', utils.base64('test')]; + this.connection.notes.allowed_auth_methods = ['PLAIN','LOGIN']; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, false); + assert.equal(this.connection.notes.auth_login_userlogin, 'test'); + assert.equal(this.connection.notes.auth_login_asked_login , true); + + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + assert.equal(this.connection.relaying, true); + assert.equal(this.connection.notes.auth_login_userlogin, null); + assert.equal(this.connection.notes.auth_login_asked_login , false); + + this.plugin.hook_unrecognized_command((code) => { + assert.equal(code, OK); + done(); + }, this.connection, ['AUTH','LOGIN']); + }, this.connection, [utils.base64('testpass')]); + }, this.connection, params); + }) + }) + + describe('hexi', () => { + beforeEach(_set_up) + + it('hexi', () => { + assert.equal(this.plugin.hexi(512), 200); + assert.equal(this.plugin.hexi(8), 8); + }) + }) + + describe('constrain_sender', () => { + beforeEach(_set_up) + + it('constrain_sender, domain match', (done) => { + this.mfrom = new Address('user@example.com') + this.connection.results.add({name: 'auth'}, { user: 'user@example.com' }) + this.plugin.constrain_sender((resCode) => { + assert.equal(resCode, undefined) + done(); + }, + this.connection, + [this.mfrom], + ) + }) + + it('constrain_sender, domain mismatch', (done) => { + this.mfrom = new Address('user@example.net') + this.connection.results.add({name: 'auth'}, { user: 'user@example.com' }) + this.plugin.constrain_sender((resCode, denyMsg) => { + assert.equal(resCode, DENY) + assert.ok(denyMsg) + done(); + }, + this.connection, + [this.mfrom], + ) + }) + it('constrain_sender, no domain', (done) => { + this.mfrom = new Address('user@example.com') + this.connection.results.add({name: 'auth'}, { user: 'user' }) + this.plugin.constrain_sender((resCode) => { + assert.equal(resCode, undefined) + done(); + }, + this.connection, + [this.mfrom], + ) + }) + }) +}) diff --git a/tests/plugins/auth/auth_vpopmaild.js b/tests/plugins/auth/auth_vpopmaild.js index 01e88da9c..10f9da76e 100644 --- a/tests/plugins/auth/auth_vpopmaild.js +++ b/tests/plugins/auth/auth_vpopmaild.js @@ -1,15 +1,16 @@ 'use strict'; -const path = require('path'); +const assert = require('node:assert') +const path = require('node:path'); -const fixtures = require('haraka-test-fixtures'); +const fixtures = require('haraka-test-fixtures'); -function _set_up (done) { +const _set_up = (done) => { this.backup = {}; - // needed for tests this.plugin = new fixtures.plugin('auth/auth_vpopmaild'); this.plugin.inherits('auth/auth_base'); + // reset the config/root_path this.plugin.config.root_path = path.resolve(__dirname, '../../../config'); this.plugin.cfg = this.plugin.config.get('auth_vpopmaild.ini'); @@ -20,72 +21,63 @@ function _set_up (done) { done(); } -exports.hook_capabilities = { - setUp : _set_up, - 'no TLS' (test) { - const cb = function (rc, msg) { - test.expect(3); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.equal(null, this.connection.capabilities); - test.done(); - }.bind(this); - this.plugin.hook_capabilities(cb, this.connection); - }, - 'with TLS' (test) { - const cb = function (rc, msg) { - test.expect(3); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.capabilities.length); - // console.log(this.connection.capabilities); - test.done(); - }.bind(this); +describe('hook_capabilities', () => { + beforeEach(_set_up) + + it('no TLS', (done) => { + this.plugin.hook_capabilities((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.equal(null, this.connection.capabilities); + done(); + }, this.connection); + }) + + it('with TLS', (done) => { this.connection.tls.enabled=true; this.connection.capabilities=[]; - this.plugin.hook_capabilities(cb, this.connection); - }, - 'with TLS, sysadmin' (test) { - const cb = function (rc, msg) { - test.expect(3); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.capabilities.length); - // console.log(this.connection.capabilities); - test.done(); - }.bind(this); + this.plugin.hook_capabilities((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.capabilities.length); + done(); + }, this.connection); + }) + + it('with TLS, sysadmin', (done) => { this.connection.tls.enabled=true; this.connection.capabilities=[]; - this.plugin.hook_capabilities(cb, this.connection); - }, -} + this.plugin.hook_capabilities((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.capabilities.length); + done(); + }, this.connection); + }) +}) -exports.get_vpopmaild_socket = { - setUp : _set_up, - 'any' (test) { - test.expect(1); +describe('get_vpopmaild_socket', () => { + beforeEach(_set_up) + + it('any', () => { const socket = this.plugin.get_vpopmaild_socket('foo@localhost.com'); - // console.log(socket); - test.ok(socket); + assert.ok(socket); socket.end(); - test.done(); - } -} + }) +}) -exports.get_plain_passwd = { - setUp : _set_up, - 'matt@example.com' (test) { - function cb (pass) { - test.expect(1); - test.ok(pass); - test.done(); - } +describe('get_plain_passwd', () => { + beforeEach(_set_up) + + it('matt@example.com', (done) => { if (this.plugin.cfg['example.com'].sysadmin) { - this.plugin.get_plain_passwd('matt@example.com', cb); + this.plugin.get_plain_passwd('matt@example.com', (pass) => { + assert.ok(pass); + done(); + }); } else { - test.expect(0); - test.done(); + done(); } - } -} + }) +}) diff --git a/tests/plugins/bounce.js b/tests/plugins/bounce.js index 009505bba..9ab2463a2 100644 --- a/tests/plugins/bounce.js +++ b/tests/plugins/bounce.js @@ -1,4 +1,5 @@ 'use strict'; +const assert = require('node:assert') const Address = require('address-rfc2821'); const fixtures = require('haraka-test-fixtures'); @@ -6,7 +7,7 @@ const message = require('haraka-email-message') const Connection = fixtures.connection; -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('bounce'); this.plugin.cfg = { @@ -36,272 +37,247 @@ function _set_up (done) { done(); } -exports.load_configs = { - setUp : _set_up, - 'load_bounce_ini' (test) { - test.expect(3); - this.plugin.load_bounce_ini(); - test.ok(this.plugin.cfg.main); - test.ok(this.plugin.cfg.check); - test.ok(this.plugin.cfg.reject); - test.done(); - }, - 'load_bounce_bad_rcpt' (test) { - test.expect(3); - this.plugin.load_bounce_bad_rcpt(); - test.ok(this.plugin.cfg.main); - test.ok(this.plugin.cfg.check); - test.ok(this.plugin.cfg.reject); - test.done(); - }, -} +describe('plugins/bounce', () => { + + describe('load_configs', () => { + beforeEach(_set_up) -exports.reject_all = { - setUp : _set_up, - 'disabled' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address(''); - this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; - const cb = function () { - test.equal(undefined, arguments[0]); - }; - this.plugin.cfg.check.reject_all=false; - this.plugin.reject_all(cb, this.connection, new Address.Address('')); - test.done(); - }, - 'not bounce ok' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address(''); - this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; - const cb = function () { - test.equal(undefined, arguments[0]); - }; - this.plugin.cfg.check.reject_all=true; - this.plugin.reject_all(cb, this.connection, new Address.Address('')); - test.done(); - }, - 'bounce rejected' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; - const cb = function () { - test.equal(DENY, arguments[0]); - }; - this.plugin.cfg.check.reject_all=true; - this.plugin.reject_all(cb, this.connection, new Address.Address('<>')); - test.done(); - }, -} + it('load_bounce_ini', () => { + this.plugin.load_bounce_ini(); + assert.ok(this.plugin.cfg.main); + assert.ok(this.plugin.cfg.check); + assert.ok(this.plugin.cfg.reject); + }) -exports.empty_return_path = { - setUp : _set_up, - 'none' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - const cb = function () { - test.equal(undefined, arguments[0]); - }; - this.plugin.empty_return_path(cb, this.connection); - test.done(); - }, - 'has' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - this.connection.transaction.header.add('Return-Path', "Content doesn't matter"); - const cb = function () { - test.equal(DENY, arguments[0]); - }; - this.plugin.empty_return_path(cb, this.connection); - test.done(); - }, -} + it('load_bounce_bad_rcpt', () => { + this.plugin.load_bounce_bad_rcpt(); + assert.ok(this.plugin.cfg.main); + assert.ok(this.plugin.cfg.check); + assert.ok(this.plugin.cfg.reject); + }) + }) -exports.non_local_msgid = { - setUp: _set_up, - 'no_msgid_in_headers' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - this.connection.transaction.body = new message.Body(); - this.connection.transaction.body.bodytext = ''; - const cb = function () { - test.equal(DENY, arguments[0]); - } - this.plugin.non_local_msgid(cb, this.connection); - test.done(); - }, - 'no_domains_in_msgid' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - this.connection.transaction.body = new message.Body(); - this.connection.transaction.body.bodytext = 'Message-ID:'; - const cb = function () { - test.equal(DENY, arguments[0]); - } - this.plugin.non_local_msgid(cb, this.connection); - test.done(); - }, - 'invalid_domain' (test) { - test.expect(2); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - this.connection.transaction.body = new message.Body(); - this.connection.transaction.body.bodytext = 'Message-ID: '; - const cb = function () { - test.equal(DENY, arguments[0]); - test.ok(/without valid domain/.test(arguments[1])); - } - this.plugin.non_local_msgid(cb, this.connection); - test.done(); - }, - /* - commented out because the code looks bogus to me - see comment in plugins/bounce.js - @baudehlo - 'non-local': function (test) { - test.expect(2); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - this.connection.transaction.body = new message.Body(); - this.connection.transaction.body.bodytext = 'Message-ID: '; - var cb = function () { - test.equal(DENY, arguments[0]); - test.ok(/non-local Message-ID/.test(arguments[1])); - } - this.plugin.non_local_msgid(cb, this.connection); - test.done(); - }, - */ -} + describe('reject_all', () => { + beforeEach(_set_up) -exports.single_recipient = { - setUp : _set_up, - 'valid' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - const cb = function () { - test.equal(undefined, arguments[0]); - }; - this.plugin.single_recipient(cb, this.connection); - test.done(); - }, - 'invalid' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ - new Address.Address('test@good.com'), - new Address.Address('test2@good.com') - ]; - const cb = function () { - console.log(arguments[0]); - test.equal(DENY, arguments[0]); - }; - this.plugin.single_recipient(cb, this.connection); - test.done(); - }, - 'test@example.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@example.com') ]; - const cb = function () { - test.equal(undefined, arguments[0]); - }; - this.plugin.single_recipient(cb, this.connection); - test.done(); - }, - 'test@example.com,test2@example.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ - new Address.Address('test1@example.com'), - new Address.Address('test2@example.com'), - ]; - const cb = function () { - test.equal(DENY, arguments[0]); - }; - this.plugin.single_recipient(cb, this.connection); - test.done(); - }, -} + it('disabled', (done) => { + this.connection.transaction.mail_from= new Address.Address(''); + this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; + this.plugin.cfg.check.reject_all=false; + this.plugin.reject_all((rc) => { + assert.equal(rc, undefined); + done() + }, this.connection, new Address.Address('')); + }) -exports.bad_rcpt = { - setUp : _set_up, - 'test@good.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; - function cb (rc) { - test.equal(undefined, rc); - } - this.plugin.bad_rcpt(cb, this.connection); - test.done(); - }, - 'test@bad1.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ new Address.Address('test@bad1.com') ]; - function cb (rc) { - test.equal(DENY, rc); - } - this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; - this.plugin.bad_rcpt(cb, this.connection); - test.done(); - }, - 'test@bad1.com, test@bad2.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ - new Address.Address('test@bad1.com'), - new Address.Address('test@bad2.com') - ]; - function cb (rc) { - test.equal(DENY, rc); - } - this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; - this.plugin.bad_rcpt(cb, this.connection); - test.done(); - }, - 'test@good.com, test@bad2.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - this.connection.transaction.rcpt_to= [ - new Address.Address('test@good.com'), - new Address.Address('test@bad2.com') - ]; - function cb (rc) { - test.equal(DENY, rc); - } - this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; - this.plugin.bad_rcpt(cb, this.connection); - test.done(); - }, -} + it('not bounce ok', (done) => { + this.connection.transaction.mail_from= new Address.Address(''); + this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; + this.plugin.cfg.check.reject_all=true; + this.plugin.reject_all((code) => { + assert.equal(code, undefined); + done() + }, this.connection, new Address.Address('')); + }) -exports.has_null_sender = { - setUp : _set_up, - '<>' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('<>'); - test.equal(true, this.plugin.has_null_sender(this.connection)); - test.done(); - }, - ' ' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address(''); - test.equal(true, this.plugin.has_null_sender(this.connection)); - test.done(); - }, - 'user@example' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('user@example'); - test.equal(false, this.plugin.has_null_sender(this.connection)); - test.done(); - }, - 'user@example.com' (test) { - test.expect(1); - this.connection.transaction.mail_from= new Address.Address('user@example.com'); - test.equal(false, this.plugin.has_null_sender(this.connection)); - test.done(); - }, -} + it('bounce rejected', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@any.com') ]; + this.plugin.cfg.check.reject_all=true; + this.plugin.reject_all((code) => { + assert.equal(code, DENY); + done() + }, this.connection, new Address.Address('<>')); + }) + }) + + describe('empty_return_path', () => { + beforeEach(_set_up) + + it('none', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.plugin.empty_return_path((rc) => { + assert.equal(rc, undefined); + done() + }, this.connection); + }) + + it('has', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.connection.transaction.header.add('Return-Path', "Content doesn't matter"); + this.plugin.empty_return_path((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + }) + + describe('non_local_msgid', () => { + beforeEach(_set_up) + + it('no_msgid_in_headers', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.connection.transaction.body = new message.Body(); + this.connection.transaction.body.bodytext = ''; + this.plugin.non_local_msgid((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + + it('no_domains_in_msgid', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.connection.transaction.body = new message.Body(); + this.connection.transaction.body.bodytext = 'Message-ID:'; + this.plugin.non_local_msgid((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + + it('invalid_domain', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.connection.transaction.body = new message.Body(); + this.connection.transaction.body.bodytext = 'Message-ID: '; + this.plugin.non_local_msgid((rc, msg) => { + assert.equal(rc, DENY); + assert.ok(/without valid domain/.test(msg)); + done() + }, this.connection); + }) + /* - commented out because the code looks bogus to me - see comment in plugins/bounce.js - @baudehlo + it('non-local': function, () => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.connection.transaction.body = new message.Body(); + this.connection.transaction.body.bodytext = 'Message-ID: '; + this.plugin.non_local_msgid((rc, msg) { + assert.equal(rc, DENY); + assert.ok(/non-local Message-ID/.test(msg)); + }, this.connection); + }) + */ + }) + + describe('single_recipient', () => { + beforeEach(_set_up) + + it('valid', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.plugin.single_recipient((rc) => { + assert.equal(rc, undefined); + done() + }, this.connection); + }) + it('invalid', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ + new Address.Address('test@good.com'), + new Address.Address('test2@good.com') + ]; + this.plugin.single_recipient((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + it('test@example.com', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@example.com') ]; + this.plugin.single_recipient((rc) => { + assert.equal(rc, undefined); + done() + }, this.connection); + }) + + it('test@example.com,test2@example.com', (done) => { + this.connection.transaction.mail_from = new Address.Address('<>'); + this.connection.transaction.rcpt_to = [ + new Address.Address('test1@example.com'), + new Address.Address('test2@example.com'), + ]; + this.plugin.single_recipient((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + }) + + describe('bad_rcpt', () => { + beforeEach(_set_up) + + it('test@good.com', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@good.com') ]; + this.plugin.bad_rcpt((rc) => { + assert.equal(rc, undefined); + done() + }, this.connection); + }) + + it('test@bad1.com', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ new Address.Address('test@bad1.com') ]; + this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; + this.plugin.bad_rcpt((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + + it('test@bad1.com, test@bad2.com', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ + new Address.Address('test@bad1.com'), + new Address.Address('test@bad2.com') + ]; + this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; + this.plugin.bad_rcpt((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + + it('test@good.com, test@bad2.com', (done) => { + this.connection.transaction.mail_from= new Address.Address('<>'); + this.connection.transaction.rcpt_to= [ + new Address.Address('test@good.com'), + new Address.Address('test@bad2.com') + ]; + this.plugin.cfg.invalid_addrs = {'test@bad1.com': true, 'test@bad2.com': true }; + this.plugin.bad_rcpt((rc) => { + assert.equal(rc, DENY); + done() + }, this.connection); + }) + }) + + describe('has_null_sender', () => { + beforeEach(_set_up) + + it('<>', () => { + this.connection.transaction.mail_from= new Address.Address('<>'); + assert.equal(this.plugin.has_null_sender(this.connection), true); + }) + + it(' ', () => { + this.connection.transaction.mail_from= new Address.Address(''); + assert.equal(this.plugin.has_null_sender(this.connection), true); + }) + + it('user@example', () => { + this.connection.transaction.mail_from= new Address.Address('user@example'); + assert.equal(this.plugin.has_null_sender(this.connection), false); + }) + + it('user@example.com', () => { + this.connection.transaction.mail_from= new Address.Address('user@example.com'); + assert.equal(this.plugin.has_null_sender(this.connection), false); + }) + }) +}) diff --git a/tests/plugins/clamd.js b/tests/plugins/clamd.js index 1f465a6cb..be02e069e 100644 --- a/tests/plugins/clamd.js +++ b/tests/plugins/clamd.js @@ -1,224 +1,208 @@ 'use strict'; +const assert = require('node:assert') +const net = require('node:net'); -const net = require('net'); -const fixtures = require('haraka-test-fixtures'); +const fixtures = require('haraka-test-fixtures'); -const Connection = fixtures.connection; - -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('clamd'); this.plugin.register(); - this.connection = Connection.createConnection(); + this.connection = fixtures.connection.createConnection(); this.connection.init_transaction(); done(); } -exports.load_clamd_ini = { - setUp : _set_up, - 'none' (test) { - test.expect(1); - test.deepEqual([], this.plugin.skip_list); - test.done(); - }, - 'defaults' (test) { - test.expect(6); - const cfg = this.plugin.cfg.main; - test.equal('localhost:3310', cfg.clamd_socket); - test.equal(30, cfg.timeout); - test.equal(10, cfg.connect_timeout); - test.equal(26214400, cfg.max_size); - test.equal(false, cfg.only_with_attachments); - test.equal(false, cfg.randomize_host_order); - test.done(); - }, - 'reject opts' (test) { - test.expect(14); - test.equal(true, this.plugin.rejectRE.test('Encrypted.')); - test.equal(true, this.plugin.rejectRE.test('Heuristics.Structured.')); - test.equal(true, this.plugin.rejectRE.test( - 'Heuristics.Structured.CreditCardNumber')); - test.equal(true, this.plugin.rejectRE.test('Broken.Executable.')); - test.equal(true, this.plugin.rejectRE.test('PUA.')); - test.equal(true, this.plugin.rejectRE.test( - 'Heuristics.OLE2.ContainsMacros')); - test.equal(true, this.plugin.rejectRE.test('Heuristics.Safebrowsing.')); - test.equal(true, this.plugin.rejectRE.test( - 'Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net')); - test.equal(true, this.plugin.rejectRE.test( - 'Sanesecurity.Junk.50402.UNOFFICIAL')); - test.equal(false, this.plugin.rejectRE.test( - 'Sanesecurity.UNOFFICIAL.oops')); - test.equal(false, this.plugin.rejectRE.test('Phishing')); - test.equal(false, this.plugin.rejectRE.test( - 'Heuristics.Phishing.Email.SpoofedDomain')); - test.equal(false, this.plugin.rejectRE.test('Suspect.Executable')); - test.equal(false, this.plugin.rejectRE.test('MattWuzHere')); - test.done(); - }, -} +describe('plugins/clamd', () => { -exports.hook_data = { - setUp : _set_up, - 'only_with_attachments, false' (test) { - test.expect(2); - test.equal(false, this.plugin.cfg.main.only_with_attachments); - const next = function () { - test.equal(false, this.connection.transaction.parse_body); - test.done(); - }.bind(this); - this.plugin.hook_data(next, this.connection); - }, - 'only_with_attachments, true' (test) { - this.plugin.cfg.main.only_with_attachments=true; - test.expect(2); - this.connection.transaction.attachment_hooks = () => {}; - const next = function () { - test.equal(true, this.plugin.cfg.main.only_with_attachments); - test.equal(true, this.connection.transaction.parse_body); - test.done(); - }.bind(this); - this.plugin.hook_data(next, this.connection); - }, -} + describe('load_clamd_ini', () => { + beforeEach(_set_up) -exports.hook_data_post = { - setUp : _set_up, - 'skip attachment' (test) { - this.connection.transaction.notes = { clamd_found_attachment: false }; - this.plugin.cfg.main.only_with_attachments=true; - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'skip authenticated' (test) { - this.connection.notes.auth_user = 'user'; - this.plugin.cfg.check.authenticated = false; - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'checks local IP' (test) { - this.connection.remote.is_local = true; - this.plugin.cfg.check.local_ip = true; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length === 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'skips local IP' (test) { - this.connection.remote.is_local = true; - this.plugin.cfg.check.local_ip = false; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'checks private IP' (test) { - this.connection.remote.is_private = true; - this.plugin.cfg.check.private_ip = true; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length === 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'skips private IP' (test) { - this.connection.remote.is_private = true; - this.plugin.cfg.check.private_ip = false; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'checks public ip' (test) { - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length === 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'skip localhost if check.local_ip = false and check.private_ip = true' (test) { - this.connection.remote.is_local = true; - this.connection.remote.is_private = true; - - this.plugin.cfg.check.local_ip = false; - this.plugin.cfg.check.private_ip = true; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'checks localhost if check.local_ip = true and check.private_ip = false' (test) { - this.connection.remote.is_local = true; - this.connection.remote.is_private = true; - - this.plugin.cfg.check.local_ip = true; - this.plugin.cfg.check.private_ip = false; - - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length === 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, - 'message too big' (test) { - this.connection.transaction.data_bytes=513; - this.plugin.cfg.main.max_size=512; - test.expect(1); - const next = function () { - test.ok(this.connection.transaction.results.get('clamd').skip.length > 0); - test.done(); - }.bind(this); - this.plugin.hook_data_post(next, this.connection); - }, -} + it('none', () => { + assert.deepEqual([], this.plugin.skip_list); + }) -exports.send_clamd_predata = { - setUp : _set_up, - 'writes the proper commands to clamd socket' (test) { - test.expect(1); - const server = new net.createServer((socket) => { - socket.on('data', (data) => { - test.ok(data.toString(), `zINSTREAM\0Received: from Haraka clamd plugin\r\n`) - // console.log(`${data.toString()}`) - }) - socket.on('end', () => { - test.done() - }) + it('defaults', () => { + const cfg = this.plugin.cfg.main; + assert.equal('localhost:3310', cfg.clamd_socket); + assert.equal(30, cfg.timeout); + assert.equal(10, cfg.connect_timeout); + assert.equal(26214400, cfg.max_size); + assert.equal(false, cfg.only_with_attachments); + assert.equal(false, cfg.randomize_host_order); + }) + + it('reject opts', () => { + assert.equal(true, this.plugin.rejectRE.test('Encrypted.')); + assert.equal(true, this.plugin.rejectRE.test('Heuristics.Structured.')); + assert.equal(true, this.plugin.rejectRE.test( + 'Heuristics.Structured.CreditCardNumber')); + assert.equal(true, this.plugin.rejectRE.test('Broken.Executable.')); + assert.equal(true, this.plugin.rejectRE.test('PUA.')); + assert.equal(true, this.plugin.rejectRE.test( + 'Heuristics.OLE2.ContainsMacros')); + assert.equal(true, this.plugin.rejectRE.test('Heuristics.Safebrowsing.')); + assert.equal(true, this.plugin.rejectRE.test( + 'Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net')); + assert.equal(true, this.plugin.rejectRE.test( + 'Sanesecurity.Junk.50402.UNOFFICIAL')); + assert.equal(false, this.plugin.rejectRE.test( + 'Sanesecurity.UNOFFICIAL.oops')); + assert.equal(false, this.plugin.rejectRE.test('Phishing')); + assert.equal(false, this.plugin.rejectRE.test( + 'Heuristics.Phishing.Email.SpoofedDomain')); + assert.equal(false, this.plugin.rejectRE.test('Suspect.Executable')); + assert.equal(false, this.plugin.rejectRE.test('MattWuzHere')); + }) + }) + + describe('hook_data', () => { + beforeEach(_set_up) + + it('only_with_attachments, false', (done) => { + assert.equal(false, this.plugin.cfg.main.only_with_attachments); + this.plugin.hook_data(() => { + assert.equal(false, this.connection.transaction.parse_body); + done(); + }, this.connection); + }) + + it('only_with_attachments, true', (done) => { + this.plugin.cfg.main.only_with_attachments=true; + this.connection.transaction.attachment_hooks = () => {}; + this.plugin.hook_data(() => { + assert.equal(true, this.plugin.cfg.main.only_with_attachments); + assert.equal(true, this.connection.transaction.parse_body); + done(); + }, this.connection); + }) + }) + + describe('hook_data_post', () => { + beforeEach(_set_up) + + it('skip attachment', (done) => { + this.connection.transaction.notes = { clamd_found_attachment: false }; + this.plugin.cfg.main.only_with_attachments=true; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); + }) + + it('skip authenticated', (done) => { + this.connection.notes.auth_user = 'user'; + this.plugin.cfg.check.authenticated = false; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); + }) + + it('checks local IP', (done) => { + this.connection.remote.is_local = true; + this.plugin.cfg.check.local_ip = true; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length === 0); + done(); + }, this.connection); + }) + + it('skips local IP', (done) => { + this.connection.remote.is_local = true; + this.plugin.cfg.check.local_ip = false; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); }) - server.listen(65535, () => { - const client = new net.Socket(); - client.connect(65535, () => { - this.plugin.send_clamd_predata(client, () => { - client.end() + it('checks private IP', (done) => { + this.connection.remote.is_private = true; + this.plugin.cfg.check.private_ip = true; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length === 0); + done(); + }, this.connection); + }) + + it('skips private IP', (done) => { + this.connection.remote.is_private = true; + this.plugin.cfg.check.private_ip = false; + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); + }) + + it('checks public ip', (done) => { + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length === 0); + done(); + }, this.connection); + }) + + it('skip localhost if check.local_ip = false and check.private_ip = true', (done) => { + this.connection.remote.is_local = true; + this.connection.remote.is_private = true; + + this.plugin.cfg.check.local_ip = false; + this.plugin.cfg.check.private_ip = true; + + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); + }) + + it('checks localhost if check.local_ip = true and check.private_ip = false', (done) => { + this.connection.remote.is_local = true; + this.connection.remote.is_private = true; + + this.plugin.cfg.check.local_ip = true; + this.plugin.cfg.check.private_ip = false; + + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length === 0); + done(); + }, this.connection); + }) + + it('message too big', (done) => { + this.connection.transaction.data_bytes=513; + this.plugin.cfg.main.max_size=512; + + this.plugin.hook_data_post(() => { + assert.ok(this.connection.transaction.results.get('clamd').skip.length > 0); + done(); + }, this.connection); + }) + }) + + describe('send_clamd_predata', () => { + beforeEach(_set_up) + + it('writes the proper commands to clamd socket', (done) => { + const server = new net.createServer((socket) => { + socket.on('data', (data) => { + assert.ok(data.toString(), `zINSTREAM\0Received: from Haraka clamd plugin\r\n`) + // console.log(`${data.toString()}`) + }) + socket.on('end', () => { + done() + }) + }) + + server.listen(65535, () => { + const client = new net.Socket(); + client.connect(65535, () => { + this.plugin.send_clamd_predata(client, () => { + client.end() + }) }) }) }) - }, -} + }) +}) diff --git a/tests/plugins/deprecated/relay_acl.js b/tests/plugins/deprecated/relay_acl.js deleted file mode 100644 index 60fdea448..000000000 --- a/tests/plugins/deprecated/relay_acl.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -const fixtures = require('haraka-test-fixtures'); - -function _set_up (done) { - - this.plugin = new fixtures.plugin('relay_acl'); - this.plugin.cfg = {}; - - this.connection = fixtures.connection.createConnection(); - this.connection.transaction = { - results: new fixtures.results(this.connection), - }; - - done(); -} - -exports.is_acl_allowed = { - setUp : _set_up, - 'bare IP' (test) { - test.expect(3); - this.plugin.acl_allow=['127.0.0.6']; - this.connection.remote.ip='127.0.0.6'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.0.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.1.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - test.done(); - }, - 'netmask' (test) { - test.expect(3); - this.plugin.acl_allow=['127.0.0.6/24']; - this.connection.remote.ip='127.0.0.6'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.0.5'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.1.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - test.done(); - }, - 'mixed (ipv4 & ipv6 (Issue #428))' (test) { - test.expect(3); - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - - this.plugin.acl_allow=['2607:f060:b008:feed::2/64']; - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - - this.plugin.acl_allow=['127.0.0.6/24']; - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - - test.done(); - }, -} - -exports.relay_dest_domains = { - setUp : _set_up, - 'relaying' (test) { - test.expect(2); - const outer = this; - function next () { - // console.log(outer.connection.results.get('relay_acl')); - // console.log(outer.connection.transaction.results.get('relay_acl')); - test.equal(undefined, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').skip.length); - test.done(); - } - this.connection.relaying=true; - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'no config' (test) { - test.expect(2); - const outer = this; - function next () { - test.equal(undefined, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').skip.length); - test.done(); - } - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=undef' (test) { - test.expect(2); - const outer = this; - function next () { - test.equal(DENY, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').fail.length); - test.done(); - } - this.plugin.cfg.domains = { foo: '{"action":"dunno"}' }; - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=deny' (test) { - test.expect(2); - const outer = this; - function next () { - test.equal(DENY, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').fail.length); - test.done(); - } - this.plugin.cfg.domains = { foo: '{"action":"deny"}' }; - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=continue' (test) { - test.expect(2); - const outer = this; - function next () { - test.equal(CONT, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').pass.length); - test.done(); - } - this.plugin.cfg.domains = { foo: '{"action":"continue"}' }; - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=accept' (test) { - test.expect(2); - const outer = this; - function next () { - test.equal(CONT, arguments[0]); - test.equal(1, outer.connection.transaction.results.get('relay_acl').pass.length); - test.done(); - } - this.plugin.cfg.domains = { foo: '{"action":"continue"}' }; - this.plugin.relay_dest_domains(next, this.connection, [{host:'foo'}]); - }, -} - -exports.refresh_config = { - setUp : _set_up, - 'callback' (test) { - test.expect(1); - function next () { - test.equal(undefined, arguments[0]); - test.done(); - } - this.plugin.refresh_config(next, this.connection); - }, -} diff --git a/tests/plugins/deprecated/relay_all.js b/tests/plugins/deprecated/relay_all.js deleted file mode 100644 index 05bd2dda6..000000000 --- a/tests/plugins/deprecated/relay_all.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -const fixtures = require('haraka-test-fixtures'); - -function _set_up (callback) { - - this.plugin = new fixtures.plugin('relay_all'); - this.connection = fixtures.connection.createConnection(); - this.params = ['foo@bar.com']; - - this.plugin.register(); - - callback(); -} - -exports.relay_all = { - setUp : _set_up, - 'should have register function' (test) { - test.expect(2); - test.isNotNull(this.plugin); - test.isFunction(this.plugin.register); - test.done(); - }, - 'register function should call register_hook()' (test) { - test.expect(1); - test.ok(this.plugin.register_hook.called); - test.done(); - }, - 'register_hook() should register for propper hook' (test) { - test.expect(1); - test.equals(this.plugin.register_hook.args[0], 'rcpt'); - test.done(); - }, - 'register_hook() should register available function' (test) { - test.expect(3); - test.equals(this.plugin.register_hook.args[1], 'confirm_all'); - test.isNotNull(this.plugin.confirm_all); - test.isFunction(this.plugin.confirm_all); - test.done(); - }, - 'confirm_all hook always returns OK' (test) { - function next (action) { - test.expect(1); - test.equals(action, OK); - test.done(); - } - - this.plugin.confirm_all(next, this.connection, this.params); - }, - 'confirm_all hook always sets connection.relaying to 1' (test) { - const next = function (action) { - test.expect(1); - test.equals(this.connection.relaying, 1); - test.done(); - }.bind(this); - - this.plugin.confirm_all(next, this.connection, this.params); - } -} diff --git a/tests/plugins/early_talker.js b/tests/plugins/early_talker.js index 9bf56a18f..f2fb5f19c 100644 --- a/tests/plugins/early_talker.js +++ b/tests/plugins/early_talker.js @@ -1,9 +1,9 @@ 'use strict'; +const assert = require('node:assert') const fixtures = require('haraka-test-fixtures'); -function _set_up (done) { - +const _set_up = (done) => { this.plugin = new fixtures.plugin('early_talker'); this.plugin.cfg = { main: { reject: true } }; @@ -11,105 +11,94 @@ function _set_up (done) { done(); } -function _tear_down (done) { done(); } +describe('early_talker', () => { + beforeEach(_set_up) + + it('no config', (done) => { + this.plugin.early_talker((rc, msg) => { + assert.equal(rc, undefined); + assert.equal(msg, undefined); + done(); + }, this.connection); + }) -exports.early_talker = { - setUp : _set_up, - tearDown : _tear_down, - 'no config' (test) { - test.expect(2); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - }.bind(this); - this.plugin.early_talker(next, this.connection); - }, - 'relaying' (test) { - test.expect(2); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - }.bind(this); + it('relaying', (done) => { this.plugin.pause = 1; this.connection.relaying = true; - this.plugin.early_talker(next, this.connection); - }, - 'is an early talker' (test) { - test.expect(3); + this.plugin.early_talker((rc, msg) => { + assert.equal(rc, undefined); + assert.equal(msg, undefined); + done(); + }, this.connection); + }) + + it('is an early talker', (done) => { const before = Date.now(); - const next = function (rc, msg) { - test.ok(Date.now() >= before + 1000); - test.equal(DENYDISCONNECT, rc); - test.equal('You talk too soon', msg); - test.done(); - }.bind(this); - this.plugin.pause = 1000; + this.plugin.pause = 1001; this.connection.early_talker = true; - this.plugin.early_talker(next, this.connection); - }, - 'is an early talker, reject=false' (test) { - test.expect(4); + this.plugin.early_talker((rc, msg) => { + assert.ok(Date.now() >= before + 1000); + assert.equal(rc, DENYDISCONNECT); + assert.equal(msg, 'You talk too soon'); + done(); + }, this.connection); + }) + + it('is an early talker, reject=false', (done) => { const before = Date.now(); - const next = function (rc, msg) { - test.ok(Date.now() >= before + 1000); - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.has('early_talker', 'fail', 'early')); - test.done(); - }.bind(this); this.plugin.pause = 1001; this.plugin.cfg.main.reject = false; this.connection.early_talker = true; - this.plugin.early_talker(next, this.connection); - }, - 'relay whitelisted ip' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.has('early_talker', 'skip', 'whitelist')); - test.done(); - }.bind(this); + this.plugin.early_talker((rc, msg) => { + assert.ok(Date.now() >= before + 1000); + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.has('early_talker', 'fail', 'early')); + done(); + }, this.connection); + }) + + it('relay whitelisted ip', (done) => { this.plugin.pause = 1000; this.plugin.whitelist = this.plugin.load_ip_list(['127.0.0.1']); this.connection.remote.ip = '127.0.0.1'; this.connection.early_talker = true; - this.plugin.early_talker(next, this.connection); - }, - 'relay whitelisted subnet' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.has('early_talker', 'skip', 'whitelist')); - test.done(); - }.bind(this); + this.plugin.early_talker((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.has('early_talker', 'skip', 'whitelist')); + done(); + }, this.connection); + }) + + it('relay whitelisted subnet', (done) => { this.plugin.pause = 1000; this.plugin.whitelist = this.plugin.load_ip_list(['127.0.0.0/16']); this.connection.remote.ip = '127.0.0.88'; this.connection.early_talker = true; - this.plugin.early_talker(next, this.connection); - }, - 'relay good senders' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.has('early_talker', 'skip', '+karma')); - test.done(); - }.bind(this); + this.plugin.early_talker((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.has('early_talker', 'skip', 'whitelist')); + done(); + }, this.connection); + }) + + it('relay good senders', (done) => { this.plugin.pause = 1000; this.connection.results.add('karma', {good: 10}); this.connection.early_talker = true; - this.plugin.early_talker(next, this.connection); - }, - 'test loading ip list' (test) { + this.plugin.early_talker((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.has('early_talker', 'skip', '+karma')); + done(); + }, this.connection); + }) + + it('test loading ip list', () => { const whitelist = this.plugin.load_ip_list(['123.123.123.123', '127.0.0.0/16']); - test.expect(2); - test.equal(whitelist[0][1], 32); - test.equal(whitelist[1][1], 16); - test.done(); - }, -} + assert.equal(whitelist[0][1], 32); + assert.equal(whitelist[1][1], 16); + }) +}) diff --git a/tests/plugins/greylist.js b/tests/plugins/greylist.js index cb7b70ad4..8d9cb9fae 100644 --- a/tests/plugins/greylist.js +++ b/tests/plugins/greylist.js @@ -1,10 +1,11 @@ 'use strict'; +const assert = require('node:assert') const path = require('path'); const fixtures = require('haraka-test-fixtures'); const ipaddr = require('ipaddr.js'); -const _set_up = function (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('greylist'); this.plugin.config.root_path = path.resolve(__dirname, '../../config'); @@ -23,36 +24,30 @@ const _set_up = function (done) { this.plugin.list = {"dyndom":["sgvps.net"]}; this.connection = fixtures.connection.createConnection(); - this.connection.transaction = { - results: new fixtures.results(this.connection), - }; + this.connection.init_transaction() done(); } -exports.in_list = { - setUp : _set_up, - 'inlist: mail(1)' (test) { - test.expect(1); - test.ok(this.plugin.addr_in_list('mail', 'josef@example.com')); - test.done(); - }, - 'inlist: rcpt(1)' (test) { - test.expect(1); - test.ok(this.plugin.addr_in_list('rcpt', 'josef@example.net')); - test.done(); - }, - 'inlist: dyndom(1)' (test) { - test.expect(1); - test.ok(this.plugin.domain_in_list('dyndom', 'sgvps.net')); - test.done(); - }, - 'inlist: ip(4)' (test) { - test.expect(4); - test.ok(this.plugin.ip_in_list('123.123.123.234')); - test.ok(this.plugin.ip_in_list('123.210.123.234')); - test.ok(this.plugin.ip_in_list('2a02:8204:d600:8060:7920:4040:20ee:9680')); - test.ok(this.plugin.ip_in_list('2a02:8204:d600:8060:7920:eeee::ff00')); - test.done(); - } -} +describe('greylist', () => { + beforeEach(_set_up) + + it('inlist: mail(1)', () => { + assert.ok(this.plugin.addr_in_list('mail', 'josef@example.com')); + }) + + it('inlist: rcpt(1)', () => { + assert.ok(this.plugin.addr_in_list('rcpt', 'josef@example.net')); + }) + + it('inlist: dyndom(1)', () => { + assert.ok(this.plugin.domain_in_list('dyndom', 'sgvps.net')); + }) + + it('inlist: ip(4)', () => { + assert.ok(this.plugin.ip_in_list('123.123.123.234')); + assert.ok(this.plugin.ip_in_list('123.210.123.234')); + assert.ok(this.plugin.ip_in_list('2a02:8204:d600:8060:7920:4040:20ee:9680')); + assert.ok(this.plugin.ip_in_list('2a02:8204:d600:8060:7920:eeee::ff00')); + }) +}) diff --git a/tests/plugins/helo.checks.js b/tests/plugins/helo.checks.js index 84c59f86b..519740ec2 100644 --- a/tests/plugins/helo.checks.js +++ b/tests/plugins/helo.checks.js @@ -1,9 +1,10 @@ 'use strict'; +const assert = require('node:assert') -const path = require('path'); -const fixtures = require('haraka-test-fixtures'); +const path = require('path'); +const fixtures = require('haraka-test-fixtures'); -const _set_up = function (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('helo.checks'); this.plugin.config.root_path = path.resolve('tests', 'config'); @@ -16,24 +17,23 @@ const _set_up = function (done) { done(); } -exports.init = { - setUp: _set_up, - 'ensure init is always run' (test) { - test.expect(4); - test.equal(this.plugin.register_hook.args[2][0], 'helo'); - test.equal(this.plugin.register_hook.args[2][1], 'init'); - test.equal(this.plugin.register_hook.args[3][0], 'ehlo'); - test.equal(this.plugin.register_hook.args[3][1], 'init'); - test.done(); - }, - 'hooks are registered' (test) { - test.expect(1); - test.equal(this.plugin.register_hook.args.length, 24) - test.done(); - }, - 'test config is loaded' (test) { - test.expect(1); - test.deepEqual(this.plugin.cfg, { +describe('helo.checks', () => { + + beforeEach(_set_up) + + it('init is always run', () => { + assert.equal(this.plugin.register_hook.args[2][0], 'helo'); + assert.equal(this.plugin.register_hook.args[2][1], 'init'); + assert.equal(this.plugin.register_hook.args[3][0], 'ehlo'); + assert.equal(this.plugin.register_hook.args[3][1], 'init'); + }) + + it('hooks are registered', () => { + assert.equal(this.plugin.register_hook.args.length, 24) + }) + + it('test config is loaded', () => { + assert.deepEqual(this.plugin.cfg, { main: {}, skip: { private_ip: true, whitelist: true, relaying: true }, bigco: { @@ -78,448 +78,417 @@ exports.init = { }, list_re: /^()$/i, }) - test.done(); - } -} + }) -exports.host_mismatch = { - setUp : _set_up, - 'host_mismatch, reject=false' (test) { - test.expect(2); - const outer = this; - this.plugin.init(() => {}, this.connection, 'helo.example.com'); - this.plugin.cfg.check.host_mismatch=true; - this.plugin.cfg.reject.host_mismatch=false; - this.plugin.host_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'anything'); - }, - 'host_mismatch, reject=true' (test) { - test.expect(2); - const outer = this; - this.plugin.init(() => { }, this.connection, 'helo.example.com'); - this.plugin.cfg.check.host_mismatch=true; - this.plugin.cfg.reject.host_mismatch=true; - this.plugin.host_mismatch(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'anything'); - }, -} + describe('host_mismatch', () => { + beforeEach(_set_up) -exports.proto_mismatch = { - setUp : _set_up, - 'proto_mismatch, reject=false, esmtp=false' (test) { - test.expect(2); - const outer = this; - this.plugin.init(() => {}, this.connection, 'helo.example.com'); - this.connection.esmtp = false; - this.plugin.cfg.check.proto_mismatch=true; - this.plugin.cfg.reject.proto_mismatch=false; - this.plugin.proto_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'anything', 'esmtp'); - }, - 'proto_mismatch, reject=false, esmtp=true' (test) { - test.expect(2); - const outer = this; - this.plugin.init(() => { }, this.connection, 'helo.example.com'); - this.connection.esmtp = true; - this.plugin.cfg.check.proto_mismatch=true; - this.plugin.cfg.reject.proto_mismatch=false; - this.plugin.proto_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length === 0); - test.done(); - }, this.connection, 'anything', 'esmtp'); - }, - 'proto_mismatch, reject=true' (test) { - test.expect(2); - const outer = this; - this.plugin.init(() => { }, this.connection, 'helo.example.com'); - this.connection.esmtp = false; - this.plugin.cfg.check.proto_mismatch=true; - this.plugin.cfg.reject.proto_mismatch=true; - this.plugin.proto_mismatch(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'anything', 'esmtp'); - }, -} + it('host_mismatch, reject=false', (done) => { + this.plugin.init(() => {}, this.connection, 'helo.example.com'); + this.plugin.cfg.check.host_mismatch=true; + this.plugin.cfg.reject.host_mismatch=false; + this.plugin.host_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'anything'); + }) -exports.rdns_match = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const outer = this; - this.connection.remote.host='helo.example.com'; - this.plugin.cfg.check.rdns_match=true; - this.plugin.cfg.reject.rdns_match=true; - this.plugin.rdns_match(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, 'helo.example.com'); - }, - 'pass (org dom match)' (test) { - test.expect(2); - const outer = this; - this.connection.remote.host='ehlo.example.com'; - this.plugin.cfg.check.rdns_match=true; - this.plugin.cfg.reject.rdns_match=false; - this.plugin.rdns_match(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, 'helo.example.com'); - }, - 'fail' (test) { - test.expect(2); - const outer = this; - this.connection.remote.host='ehlo.gmail.com'; - this.plugin.cfg.check.rdns_match=true; - this.plugin.cfg.reject.rdns_match=false; - this.plugin.rdns_match(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'helo.example.com'); - }, - 'fail, reject' (test) { - test.expect(2); - const outer = this; - this.connection.remote.host='ehlo.gmail.com'; - this.plugin.cfg.check.rdns_match=true; - this.plugin.cfg.reject.rdns_match=true; - this.plugin.rdns_match(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, 'helo.example.com'); - }, -} + it('host_mismatch, reject=true', (done) => { + this.plugin.init(() => { }, this.connection, 'helo.example.com'); + this.plugin.cfg.check.host_mismatch=true; + this.plugin.cfg.reject.host_mismatch=true; + this.plugin.host_mismatch((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'anything'); + }) + }) -exports.bare_ip = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const outer = this; - this.plugin.cfg.check.bare_ip=true; - this.plugin.cfg.reject.bare_ip=true; - this.plugin.bare_ip(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, '[192.168.1.2]'); - }, - 'fail' (test) { - test.expect(2); - const outer = this; - this.plugin.cfg.check.bare_ip=true; - this.plugin.cfg.reject.bare_ip=false; - this.plugin.bare_ip(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, '192.168.1.1'); - }, - 'fail, reject' (test) { - test.expect(2); - const outer = this; - this.plugin.cfg.check.bare_ip=true; - this.plugin.cfg.reject.bare_ip=true; - this.plugin.bare_ip(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, '192.168.1.1'); - }, -} + describe('proto_mismatch', () => { + beforeEach(_set_up) -exports.dynamic = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const outer = this; - const test_helo = 'matt.simerson.tld'; - this.connection.remote.ip='208.75.177.99'; - this.plugin.cfg.check.dynamic=true; - this.plugin.cfg.reject.dynamic=true; - this.plugin.dynamic(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail' (test) { - test.expect(2); - const outer = this; - const test_helo = 'c-76-121-96-159.hsd1.wa.comcast.net'; - this.connection.remote.ip='76.121.96.159'; - this.plugin.cfg.check.dynamic=true; - this.plugin.cfg.reject.dynamic=false; - this.plugin.dynamic(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject' (test) { - test.expect(2); - const outer = this; - const test_helo = 'c-76-121-96-159.hsd1.wa.comcast.net'; - this.connection.remote.ip='76.121.96.159'; - this.plugin.cfg.check.dynamic=true; - this.plugin.cfg.reject.dynamic=true; - this.plugin.dynamic(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, -} + it('proto_mismatch, reject=false, esmtp=false', (done) => { + this.plugin.init(() => {}, this.connection, 'helo.example.com'); + this.connection.esmtp = false; + this.plugin.cfg.check.proto_mismatch=true; + this.plugin.cfg.reject.proto_mismatch=false; + this.plugin.proto_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'anything', 'esmtp'); + }) -exports.big_company = { - setUp : _set_up, - 'pass, reject=false' (test) { - test.expect(2); - const outer = this; - const test_helo = 'yahoo.com'; - this.connection.remote.host='yahoo.com'; - this.plugin.cfg.check.big_company=true; - this.plugin.cfg.reject.big_company=true; - this.plugin.big_company(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=false' (test) { - test.expect(2); - const outer = this; - const test_helo = 'yahoo.com'; - this.connection.remote.host='anything-else.com'; - this.connection.remote.is_private=false; - this.plugin.cfg.check.big_company=true; - this.plugin.cfg.reject.big_company=false; - this.plugin.big_company(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=true' (test) { - test.expect(2); - const outer = this; - const test_helo = 'yahoo.com'; - this.connection.remote.host='anything-else.com'; - this.plugin.cfg.check.big_company=true; - this.plugin.cfg.reject.big_company=true; - this.plugin.big_company(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, -} + it('proto_mismatch, reject=false, esmtp=true', (done) => { + this.plugin.init(() => { }, this.connection, 'helo.example.com'); + this.connection.esmtp = true; + this.plugin.cfg.check.proto_mismatch=true; + this.plugin.cfg.reject.proto_mismatch=false; + this.plugin.proto_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length === 0); + done(); + }, this.connection, 'anything', 'esmtp'); + }) -exports.literal_mismatch = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const outer = this; - const test_helo = '[10.0.1.1]'; - this.connection.remote.ip='10.0.1.1'; - this.connection.remote.is_private=true; - this.plugin.cfg.check.literal_mismatch=1; - this.plugin.cfg.reject.literal_mismatch=true; - this.plugin.literal_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').skip.length); - test.done(); - }, this.connection, test_helo); - }, - 'pass, network' (test) { - test.expect(2); - const outer = this; - const test_helo = '[10.0.1.1]'; - this.connection.remote.ip='10.0.1.2'; - this.connection.remote.is_private=true; - this.plugin.cfg.check.literal_mismatch=2; - this.plugin.cfg.reject.literal_mismatch=true; - this.plugin.literal_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').skip.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=false' (test) { - test.expect(2); - const outer = this; - const test_helo = '[10.0.1.1]'; - this.connection.remote.ip='10.0.1.2'; - this.connection.remote.is_private=true; - this.plugin.cfg.check.literal_mismatch=0; - this.plugin.cfg.reject.literal_mismatch=false; - this.plugin.literal_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').skip.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=true' (test) { - test.expect(2); - const outer = this; - const test_helo = '[10.0.1.1]'; - this.connection.remote.ip='10.0.1.2'; - this.connection.remote.is_private=true; - this.plugin.cfg.check.literal_mismatch=0; - this.plugin.cfg.reject.literal_mismatch=true; - this.plugin.literal_mismatch(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').skip.length); - test.done(); - }, this.connection, test_helo); - }, -} + it('proto_mismatch, reject=true', (done) => { + this.plugin.init(() => { }, this.connection, 'helo.example.com'); + this.connection.esmtp = false; + this.plugin.cfg.check.proto_mismatch=true; + this.plugin.cfg.reject.proto_mismatch=true; + this.plugin.proto_mismatch((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'anything', 'esmtp'); + }) + }) -exports.valid_hostname = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const test_helo = 'great.domain.com'; - const outer = this; - this.plugin.cfg.check.valid_hostname=true; - this.plugin.cfg.reject.valid_hostname=true; - this.plugin.valid_hostname(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=false' (test) { - test.expect(2); - const test_helo = 'great.domain.non-existent-tld'; - const outer = this; - this.plugin.cfg.check.valid_hostname=true; - this.plugin.cfg.reject.valid_hostname=false; - this.plugin.valid_hostname(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=true' (test) { - test.expect(2); - const test_helo = 'great.domain.non-existent-tld'; - const outer = this; - this.plugin.cfg.check.valid_hostname=true; - this.plugin.cfg.reject.valid_hostname=true; - this.plugin.valid_hostname(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, -} + describe('rdns_match', () => { + beforeEach(_set_up) -exports.forward_dns = { - setUp : _set_up, - 'pass' (test) { - test.expect(2); - const outer = this; - const test_helo = 'b.resolvers.level3.net'; - this.connection.remote.ip='4.2.2.2'; - this.plugin.cfg.check.forward_dns=true; - this.plugin.cfg.reject.forward_dns=true; - this.connection.results.add(this.plugin, {pass: 'valid_hostname'}); - this.plugin.forward_dns(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=false' (test) { - test.expect(2); - const outer = this; - const test_helo = 'www.google.com'; - this.connection.remote.ip='66.128.51.163'; - this.plugin.cfg.check.forward_dns=true; - this.plugin.cfg.reject.forward_dns=false; - this.plugin.forward_dns(function () { - test.equal(undefined, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'fail, reject=true' (test) { - test.expect(2); - const outer = this; - const test_helo = 'www.google.com'; - this.connection.remote.ip = '66.128.51.163'; - this.plugin.cfg.check.forward_dns=true; - this.plugin.cfg.reject.forward_dns=true; - this.plugin.forward_dns(function () { - test.equal(DENY, arguments[0]); - test.ok(outer.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, -} + it('pass', (done) => { + this.connection.remote.host='helo.example.com'; + this.plugin.cfg.check.rdns_match=true; + this.plugin.cfg.reject.rdns_match=true; + this.plugin.rdns_match((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, 'helo.example.com'); + }) -exports.match_re = { - setUp : _set_up, - 'miss' (test) { - test.expect(3); - const test_helo = 'not_in_re_list.net'; - this.plugin.cfg.list_re = new RegExp(`^(${['bad.tld'].join('|')})$`, 'i'); - this.plugin.match_re((rc, msg) => { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.get('helo.checks').pass.length); - test.done(); - }, this.connection, test_helo); - }, - 'hit, reject=no' (test) { - test.expect(3); - const test_helo = 'ylmf-pc'; - this.plugin.cfg.reject.match_re = false; - this.plugin.cfg.list_re = new RegExp(`^(${['ylmf-pc'].join('|')})$`, 'i'); - this.plugin.match_re((rc, msg) => { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.ok(this.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'hit, reject=yes, exact' (test) { - test.expect(3); - const test_helo = 'ylmf-pc'; - this.plugin.cfg.reject.match_re = true; - this.plugin.cfg.list_re = new RegExp(`^(${['ylmf-pc'].join('|')})$`, 'i'); - this.plugin.match_re((rc, msg) => { - test.equal(DENY, rc); - test.equal('That HELO not allowed here', msg); - test.ok(this.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, - 'hit, reject=yes, pattern' (test) { - test.expect(3); - const test_helo = 'ylmf-pc'; - this.plugin.cfg.reject.match_re = true; - this.plugin.cfg.list_re = new RegExp(`^(${['ylm.*'].join('|')})$`, 'i'); - this.plugin.match_re((rc, msg) => { - test.equal(DENY, rc); - test.equal('That HELO not allowed here', msg); - test.ok(this.connection.results.get('helo.checks').fail.length); - test.done(); - }, this.connection, test_helo); - }, -} + it('pass (org dom match)', (done) => { + this.connection.remote.host='ehlo.example.com'; + this.plugin.cfg.check.rdns_match=true; + this.plugin.cfg.reject.rdns_match=false; + this.plugin.rdns_match((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, 'helo.example.com'); + }) + + it('fail', (done) => { + this.connection.remote.host='ehlo.gmail.com'; + this.plugin.cfg.check.rdns_match=true; + this.plugin.cfg.reject.rdns_match=false; + this.plugin.rdns_match((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'helo.example.com'); + }) + + it('fail, reject', (done) => { + this.connection.remote.host='ehlo.gmail.com'; + this.plugin.cfg.check.rdns_match=true; + this.plugin.cfg.reject.rdns_match=true; + this.plugin.rdns_match((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, 'helo.example.com'); + }) + }) + + describe('bare_ip', () => { + beforeEach(_set_up) + + it('pass', (done) => { + this.plugin.cfg.check.bare_ip=true; + this.plugin.cfg.reject.bare_ip=true; + this.plugin.bare_ip((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, '[192.168.1.2]'); + }) + it('fail', (done) => { + this.plugin.cfg.check.bare_ip=true; + this.plugin.cfg.reject.bare_ip=false; + this.plugin.bare_ip((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, '192.168.1.1'); + }) + it('fail, reject', (done) => { + this.plugin.cfg.check.bare_ip=true; + this.plugin.cfg.reject.bare_ip=true; + this.plugin.bare_ip((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, '192.168.1.1'); + }) + }) + + describe('dynamic', () => { + beforeEach(_set_up) + + it('pass', (done) => { + const test_helo = 'matt.simerson.tld'; + this.connection.remote.ip='208.75.177.99'; + this.plugin.cfg.check.dynamic=true; + this.plugin.cfg.reject.dynamic=true; + this.plugin.dynamic((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, test_helo); + }) + + it('fail', (done) => { + const test_helo = 'c-76-121-96-159.hsd1.wa.comcast.net'; + this.connection.remote.ip='76.121.96.159'; + this.plugin.cfg.check.dynamic=true; + this.plugin.cfg.reject.dynamic=false; + this.plugin.dynamic((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject', (done) => { + const test_helo = 'c-76-121-96-159.hsd1.wa.comcast.net'; + this.connection.remote.ip='76.121.96.159'; + this.plugin.cfg.check.dynamic=true; + this.plugin.cfg.reject.dynamic=true; + this.plugin.dynamic((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + }) + + describe('big_company', () => { + beforeEach(_set_up) + + it('pass, reject=false', (done) => { + const test_helo = 'yahoo.com'; + this.connection.remote.host='yahoo.com'; + this.plugin.cfg.check.big_company=true; + this.plugin.cfg.reject.big_company=true; + this.plugin.big_company((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=false', (done) => { + const test_helo = 'yahoo.com'; + this.connection.remote.host='anything-else.com'; + this.connection.remote.is_private=false; + this.plugin.cfg.check.big_company=true; + this.plugin.cfg.reject.big_company=false; + this.plugin.big_company((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=true', (done) => { + const test_helo = 'yahoo.com'; + this.connection.remote.host='anything-else.com'; + this.plugin.cfg.check.big_company=true; + this.plugin.cfg.reject.big_company=true; + this.plugin.big_company((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + }) + + describe('literal_mismatch', () => { + beforeEach(_set_up) + + it('pass', (done) => { + const test_helo = '[10.0.1.1]'; + this.connection.remote.ip='10.0.1.1'; + this.connection.remote.is_private=true; + this.plugin.cfg.check.literal_mismatch=1; + this.plugin.cfg.reject.literal_mismatch=true; + this.plugin.literal_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').skip.length); + done(); + }, this.connection, test_helo); + }) + + it('pass, network', (done) => { + const test_helo = '[10.0.1.1]'; + this.connection.remote.ip='10.0.1.2'; + this.connection.remote.is_private=true; + this.plugin.cfg.check.literal_mismatch=2; + this.plugin.cfg.reject.literal_mismatch=true; + this.plugin.literal_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').skip.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=false', (done) => { + const test_helo = '[10.0.1.1]'; + this.connection.remote.ip='10.0.1.2'; + this.connection.remote.is_private=true; + this.plugin.cfg.check.literal_mismatch=0; + this.plugin.cfg.reject.literal_mismatch=false; + this.plugin.literal_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').skip.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=true', (done) => { + const test_helo = '[10.0.1.1]'; + this.connection.remote.ip='10.0.1.2'; + this.connection.remote.is_private=true; + this.plugin.cfg.check.literal_mismatch=0; + this.plugin.cfg.reject.literal_mismatch=true; + this.plugin.literal_mismatch((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').skip.length); + done(); + }, this.connection, test_helo); + }) + }) + + describe('valid_hostname', () => { + beforeEach(_set_up) + + it('pass', (done) => { + const test_helo = 'great.domain.com'; + this.plugin.cfg.check.valid_hostname=true; + this.plugin.cfg.reject.valid_hostname=true; + this.plugin.valid_hostname((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=false', (done) => { + const test_helo = 'great.domain.non-existent-tld'; + this.plugin.cfg.check.valid_hostname=true; + this.plugin.cfg.reject.valid_hostname=false; + this.plugin.valid_hostname((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=true', (done) => { + const test_helo = 'great.domain.non-existent-tld'; + this.plugin.cfg.check.valid_hostname=true; + this.plugin.cfg.reject.valid_hostname=true; + this.plugin.valid_hostname((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + }) + + describe('forward_dns', () => { + beforeEach(_set_up) + + it('pass', (done) => { + const test_helo = 'b.resolvers.level3.net'; + this.connection.remote.ip='4.2.2.2'; + this.plugin.cfg.check.forward_dns=true; + this.plugin.cfg.reject.forward_dns=true; + this.connection.results.add(this.plugin, {pass: 'valid_hostname'}); + this.plugin.forward_dns((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=false', (done) => { + const test_helo = 'www.google.com'; + this.connection.remote.ip='66.128.51.163'; + this.plugin.cfg.check.forward_dns=true; + this.plugin.cfg.reject.forward_dns=false; + this.plugin.forward_dns((rc) => { + assert.equal(rc, undefined); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('fail, reject=true', (done) => { + const test_helo = 'www.google.com'; + this.connection.remote.ip = '66.128.51.163'; + this.plugin.cfg.check.forward_dns=true; + this.plugin.cfg.reject.forward_dns=true; + this.plugin.forward_dns((rc) => { + assert.equal(rc, DENY); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + }) + + describe('match_re', () => { + beforeEach(_set_up) + + it('miss', (done) => { + const test_helo = 'not_in_re_list.net'; + this.plugin.cfg.list_re = new RegExp(`^(${['bad.tld'].join('|')})$`, 'i'); + this.plugin.match_re((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.get('helo.checks').pass.length); + done(); + }, this.connection, test_helo); + }) + + it('hit, reject=no', (done) => { + const test_helo = 'ylmf-pc'; + this.plugin.cfg.reject.match_re = false; + this.plugin.cfg.list_re = new RegExp(`^(${['ylmf-pc'].join('|')})$`, 'i'); + this.plugin.match_re((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('hit, reject=yes, exact', (done) => { + const test_helo = 'ylmf-pc'; + this.plugin.cfg.reject.match_re = true; + this.plugin.cfg.list_re = new RegExp(`^(${['ylmf-pc'].join('|')})$`, 'i'); + this.plugin.match_re((rc, msg) => { + assert.equal(DENY, rc); + assert.equal('That HELO not allowed here', msg); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + + it('hit, reject=yes, pattern', (done) => { + const test_helo = 'ylmf-pc'; + this.plugin.cfg.reject.match_re = true; + this.plugin.cfg.list_re = new RegExp(`^(${['ylm.*'].join('|')})$`, 'i'); + this.plugin.match_re((rc, msg) => { + assert.equal(DENY, rc); + assert.equal('That HELO not allowed here', msg); + assert.ok(this.connection.results.get('helo.checks').fail.length); + done(); + }, this.connection, test_helo); + }) + }) +}) diff --git a/tests/plugins/mail_from.is_resolvable.js b/tests/plugins/mail_from.is_resolvable.js index a4a5dd7df..ca11b640f 100644 --- a/tests/plugins/mail_from.is_resolvable.js +++ b/tests/plugins/mail_from.is_resolvable.js @@ -1,10 +1,11 @@ 'use strict'; +const assert = require('node:assert') const fixtures = require('haraka-test-fixtures'); const dns = require('dns'); const Address = require('address-rfc2821').Address -const _set_up = function (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('mail_from.is_resolvable'); this.plugin.register(); @@ -15,18 +16,20 @@ const _set_up = function (done) { done(); } -exports.hook_mail = { - setUp : _set_up, - 'any.com, no err code' (test) { - test.expect(1); - const txn = this.connection.transaction; - this.plugin.hook_mail((code, msg) => { - console.log() - test.deepEqual(txn.results.get('mail_from.is_resolvable').pass, ['has_fwd_dns']); - test.done(); - }, - this.connection, - [new Address('')] - ); - }, -} +describe('mail_from.is_resolvable', () => { + beforeEach(_set_up) + + describe('hook_mail', () => { + it('any.com, no err code', (done) => { + const txn = this.connection.transaction; + this.plugin.hook_mail((code, msg) => { + // console.log() + assert.deepEqual(txn.results.get('mail_from.is_resolvable').pass, ['has_fwd_dns']); + done(); + }, + this.connection, + [new Address('')] + ) + }) + }) +}) diff --git a/tests/plugins/queue/smtp_forward.js b/tests/plugins/queue/smtp_forward.js index 237594421..b29b8d746 100644 --- a/tests/plugins/queue/smtp_forward.js +++ b/tests/plugins/queue/smtp_forward.js @@ -1,14 +1,14 @@ 'use strict'; +const assert = require('node:assert') +const path = require('node:path'); -const path = require('path'); - -const { Address } = require('address-rfc2821'); -const fixtures = require('haraka-test-fixtures'); -const Notes = require('haraka-notes') +const { Address } = require('address-rfc2821'); +const fixtures = require('haraka-test-fixtures'); +const Notes = require('haraka-notes') const OK = 906; -function _setup (done) { +const _setup = (done) => { this.plugin = new fixtures.plugin('queue/smtp_forward'); // switch config directory to 'tests/config' @@ -23,199 +23,184 @@ function _setup (done) { done(); } -exports.loadingTLSConfig = { - 'TLS enabled but no outbound config in tls.ini': test => { - const plugin = new fixtures.plugin('queue/smtp_forward'); - test.expect(2); - - plugin.register(); - - test.equal(plugin.tls_options, undefined); - test.equal(plugin.register_hook.called, true); - - test.done(); - }, -} - -exports.register = { - setUp : _setup, - 'register' (test) { - test.expect(1); - this.plugin.register(); - test.ok(this.plugin.cfg.main); - test.done(); - }, -} - -exports.get_config = { - setUp : _setup, - 'no recipient' (test) { - test.expect(3); - const cfg = this.plugin.get_config(this.connection); - test.equal(cfg.host, 'localhost'); - test.equal(cfg.enable_tls, true); - test.equal(cfg.one_message_per_rcpt, true); - test.done(); - }, - 'null recipient' (test) { - test.expect(3); - this.connection.transaction.rcpt_to.push(new Address('<>')); - const cfg = this.plugin.get_config(this.connection); - test.equal(cfg.host, 'localhost'); - test.equal(cfg.enable_tls, true); - test.equal(cfg.one_message_per_rcpt, true); - test.done(); - }, - 'valid recipient' (test) { - test.expect(3); - this.connection.transaction.rcpt_to.push( - new Address('') - ); - const cfg = this.plugin.get_config(this.connection); - test.equal(cfg.enable_tls, true); - test.equal(cfg.one_message_per_rcpt, true); - test.equal(cfg.host, 'localhost'); - test.done(); - }, - 'valid recipient with route' (test) { - test.expect(1); - this.connection.transaction.rcpt_to.push( - new Address('') - ); - test.deepEqual(this.plugin.get_config(this.connection), { - host: '1.2.3.4', - enable_tls: true, - auth_user: 'postmaster@test.com', - auth_pass: 'superDuperSecret', - }); - test.done(); - }, - 'valid recipient with route & diff config' (test) { - test.expect(1); - this.connection.transaction.rcpt_to.push( - new Address('') - ); - const cfg = this.plugin.get_config(this.connection); - test.deepEqual(cfg, { - host: '1.2.3.4', - enable_tls: false - }); - test.done(); - }, - 'valid 2 recipients with same route' (test) { - test.expect(1); - this.connection.transaction.rcpt_to.push( - new Address(''), - new Address('') - ); - const cfg = this.plugin.get_config(this.connection); - test.deepEqual(cfg.host, '1.2.3.4' ); - test.done(); - }, - 'null sender' (test) { - test.expect(3); - this.plugin.cfg.main.domain_selector = 'mail_from'; - this.connection.transaction.mail_from = new Address('<>'); - const cfg = this.plugin.get_config(this.connection); - test.equal(cfg.host, 'localhost'); - test.equal(cfg.enable_tls, true); - test.equal(cfg.one_message_per_rcpt, true); - test.done(); - }, - 'return mail_from domain configuration' (test) { - test.expect(1); - this.connection.transaction.mail_from = new Address(''); - this.plugin.cfg.main.domain_selector = 'mail_from'; - const cfg = this.plugin.get_config(this.connection); - test.deepEqual(cfg.host, '2.3.4.5'); - delete this.plugin.cfg.main.domain_selector; // clear this for future tests - test.done(); - } -} - -exports.get_mx = { - setUp : _setup, - 'returns no outbound route for undefined domains' (test) { - test.expect(2); - this.plugin.get_mx((code, mx) => { - test.equal(code, undefined); - test.deepEqual(mx, undefined); - test.done(); - }, this.hmail, 'undefined.com'); - }, - 'returns no outbound route when queue.wants !== smtp_forward' (test) { - test.expect(2); - this.hmail.todo.notes.set('queue.wants', 'outbound') - this.hmail.todo.notes.set('queue.next_hop', 'smtp://5.4.3.2:26') - this.plugin.get_mx((code, mx) => { - test.equal(code, undefined); - test.deepEqual(mx, undefined); - test.done(); - }, this.hmail, 'undefined.com'); - }, - 'returns an outbound route for defined domains' (test) { - test.expect(2); - this.plugin.get_mx((code, mx) => { - test.equal(code, OK); - test.deepEqual(mx, { - priority: 0, exchange: '1.2.3.4', port: 2555, +describe('smtp_forward', () => { + describe('tls config', () => { + it('TLS enabled but no outbound config in tls.ini', () => { + const plugin = new fixtures.plugin('queue/smtp_forward'); + plugin.register(); + + assert.equal(plugin.tls_options, undefined); + assert.equal(plugin.register_hook.called, true); + }) + }) + + describe('register', () => { + beforeEach(_setup) + + it('register', () => { + this.plugin.register(); + assert.ok(this.plugin.cfg.main); + }) + }) + + describe('get_config', () => { + beforeEach(_setup) + + it('no recipient', () => { + const cfg = this.plugin.get_config(this.connection); + assert.equal(cfg.host, 'localhost'); + assert.equal(cfg.enable_tls, true); + assert.equal(cfg.one_message_per_rcpt, true); + }) + + it('null recipient', () => { + this.connection.transaction.rcpt_to.push(new Address('<>')); + const cfg = this.plugin.get_config(this.connection); + assert.equal(cfg.host, 'localhost'); + assert.equal(cfg.enable_tls, true); + assert.equal(cfg.one_message_per_rcpt, true); + }) + + it('valid recipient', () => { + this.connection.transaction.rcpt_to.push( + new Address('') + ); + const cfg = this.plugin.get_config(this.connection); + assert.equal(cfg.enable_tls, true); + assert.equal(cfg.one_message_per_rcpt, true); + assert.equal(cfg.host, 'localhost'); + }) + + it('valid recipient with route', () => { + this.connection.transaction.rcpt_to.push( + new Address('') + ); + assert.deepEqual(this.plugin.get_config(this.connection), { + host: '1.2.3.4', + enable_tls: true, auth_user: 'postmaster@test.com', - auth_pass: 'superDuperSecret' + auth_pass: 'superDuperSecret', }); - test.done(); - }, this.hmail, 'test.com'); - }, - 'is enabled when queue.wants is set' (test) { - test.expect(2); - this.hmail.todo.notes.set('queue.wants', 'smtp_forward') - this.hmail.todo.notes.set('queue.next_hop', 'smtp://4.3.2.1:465') - this.plugin.get_mx((code, mx) => { - test.equal(code, OK); - test.deepEqual(mx, { priority: 0, port: 465, exchange: '4.3.2.1' }); - test.done(); - }, this.hmail, 'undefined.com'); - }, - 'sets using_lmtp when next_hop URL is lmtp' (test) { - test.expect(2); - this.hmail.todo.notes.set('queue.wants', 'smtp_forward') - this.hmail.todo.notes.set('queue.next_hop', 'lmtp://4.3.2.1') - this.plugin.get_mx((code, mx) => { - test.equal(code, OK); - test.deepEqual(mx, { priority: 0, port: 24, using_lmtp: true, exchange: '4.3.2.1' }); - test.done(); - }, this.hmail, 'undefined.com'); - }, -} - -exports.is_outbound_enabled = { - setUp : _setup, - 'enable_outbound is false by default' (test) { - test.expect(1); - test.equal(this.plugin.is_outbound_enabled(this.plugin.cfg), false); - test.done(); - }, - 'per-domain enable_outbound is false by default' (test) { - test.expect(1); - this.connection.transaction.rcpt_to = [ new Address('') ]; - const cfg = this.plugin.get_config(this.connection); - test.equal(this.plugin.is_outbound_enabled(cfg), false); - test.done(); - }, - 'per-domain enable_outbound can be set to true' (test) { - test.expect(1); - this.plugin.cfg['test.com'].enable_outbound = true; - this.connection.transaction.rcpt_to = [ new Address('') ]; - const cfg = this.plugin.get_config(this.connection); - test.equal(this.plugin.is_outbound_enabled(cfg), true); - test.done(); - }, - 'per-domain enable_outbound is false even if top level is false' (test) { - test.expect(1); - this.plugin.cfg.main.enable_outbound = false; // this will be ignored - this.plugin.cfg['test.com'].enable_outbound = false; - this.connection.transaction.rcpt_to = [ new Address('') ]; - const cfg = this.plugin.get_config(this.connection); - test.equal(this.plugin.is_outbound_enabled(cfg), false); - test.done(); - } -} + }) + + it('valid recipient with route & diff config', () => { + this.connection.transaction.rcpt_to.push( + new Address('') + ); + const cfg = this.plugin.get_config(this.connection); + assert.deepEqual(cfg, { + host: '1.2.3.4', + enable_tls: false + }); + }) + + it('valid 2 recipients with same route', () => { + this.connection.transaction.rcpt_to.push( + new Address(''), + new Address('') + ); + const cfg = this.plugin.get_config(this.connection); + assert.deepEqual(cfg.host, '1.2.3.4' ); + }) + + it('null sender', () => { + this.plugin.cfg.main.domain_selector = 'mail_from'; + this.connection.transaction.mail_from = new Address('<>'); + const cfg = this.plugin.get_config(this.connection); + assert.equal(cfg.host, 'localhost'); + assert.equal(cfg.enable_tls, true); + assert.equal(cfg.one_message_per_rcpt, true); + }) + + it('return mail_from domain configuration', () => { + this.connection.transaction.mail_from = new Address(''); + this.plugin.cfg.main.domain_selector = 'mail_from'; + const cfg = this.plugin.get_config(this.connection); + assert.deepEqual(cfg.host, '2.3.4.5'); + delete this.plugin.cfg.main.domain_selector; // clear this for future tests + }) + }) + + describe('get_mx', () => { + beforeEach(_setup) + + it('returns no outbound route for undefined domains', (done) => { + this.plugin.get_mx((code, mx) => { + assert.equal(code, undefined); + assert.deepEqual(mx, undefined); + done(); + }, this.hmail, 'undefined.com'); + }) + + it('returns no outbound route when queue.wants !== smtp_forward', (done) => { + this.hmail.todo.notes.set('queue.wants', 'outbound') + this.hmail.todo.notes.set('queue.next_hop', 'smtp://5.4.3.2:26') + this.plugin.get_mx((code, mx) => { + assert.equal(code, undefined); + assert.deepEqual(mx, undefined); + done(); + }, this.hmail, 'undefined.com'); + }) + + it('returns an outbound route for defined domains', (done) => { + this.plugin.get_mx((code, mx) => { + assert.equal(code, OK); + assert.deepEqual(mx, { + priority: 0, exchange: '1.2.3.4', port: 2555, + auth_user: 'postmaster@test.com', + auth_pass: 'superDuperSecret' + }); + done(); + }, this.hmail, 'test.com'); + }) + + it('is enabled when queue.wants is set', (done) => { + this.hmail.todo.notes.set('queue.wants', 'smtp_forward') + this.hmail.todo.notes.set('queue.next_hop', 'smtp://4.3.2.1:465') + this.plugin.get_mx((code, mx) => { + assert.equal(code, OK); + assert.deepEqual(mx, { priority: 0, port: 465, exchange: '4.3.2.1' }); + done(); + }, this.hmail, 'undefined.com'); + }) + + it('sets using_lmtp when next_hop URL is lmtp', (done) => { + this.hmail.todo.notes.set('queue.wants', 'smtp_forward') + this.hmail.todo.notes.set('queue.next_hop', 'lmtp://4.3.2.1') + this.plugin.get_mx((code, mx) => { + assert.equal(code, OK); + assert.deepEqual(mx, { priority: 0, port: 24, using_lmtp: true, exchange: '4.3.2.1' }); + done(); + }, this.hmail, 'undefined.com'); + }) + }) + + describe('is_outbound_enabled', () => { + beforeEach(_setup) + + it('enable_outbound is false by default', () => { + assert.equal(this.plugin.is_outbound_enabled(this.plugin.cfg), false); + }) + + it('per-domain enable_outbound is false by default', () => { + this.connection.transaction.rcpt_to = [ new Address('') ]; + const cfg = this.plugin.get_config(this.connection); + assert.equal(this.plugin.is_outbound_enabled(cfg), false); + }) + + it('per-domain enable_outbound can be set to true', () => { + this.plugin.cfg['test.com'].enable_outbound = true; + this.connection.transaction.rcpt_to = [ new Address('') ]; + const cfg = this.plugin.get_config(this.connection); + assert.equal(this.plugin.is_outbound_enabled(cfg), true); + }) + + it('per-domain enable_outbound is false even if top level is false', () => { + this.plugin.cfg.main.enable_outbound = false; // this will be ignored + this.plugin.cfg['test.com'].enable_outbound = false; + this.connection.transaction.rcpt_to = [ new Address('') ]; + const cfg = this.plugin.get_config(this.connection); + assert.equal(this.plugin.is_outbound_enabled(cfg), false); + }) + }) +}) diff --git a/tests/plugins/rcpt_to.host_list_base.js b/tests/plugins/rcpt_to.host_list_base.js index a476ad34d..3ef678df5 100644 --- a/tests/plugins/rcpt_to.host_list_base.js +++ b/tests/plugins/rcpt_to.host_list_base.js @@ -1,132 +1,122 @@ 'use strict'; +const assert = require('node:assert') const { Address } = require('address-rfc2821'); const fixtures = require('haraka-test-fixtures'); -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('rcpt_to.host_list_base'); this.plugin.cfg = {}; this.plugin.host_list = {}; this.connection = fixtures.connection.createConnection(); - this.connection.transaction = { - results: new fixtures.results(this.connection), - notes: {}, - }; + this.connection.init_transaction() done(); } -exports.in_host_list = { - setUp : _set_up, - 'miss' (test) { - test.expect(1); - test.equal(false, this.plugin.in_host_list('test.com')); - test.done(); - }, - 'hit' (test) { - test.expect(1); - this.plugin.host_list['test.com'] = true; - test.equal(true, this.plugin.in_host_list('test.com')); - test.done(); - }, -} +describe('rcpt_to.host_list_base', () => { -exports.in_host_regex = { - setUp : _set_up, - 'undef' (test) { - test.expect(1); - const r = this.plugin.in_host_regex('test.com'); - test.equal(false, r); - test.done(); - }, - 'miss' (test) { - test.expect(1); - this.plugin.host_list_regex=['miss.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(false, r); - test.done(); - }, - 'exact hit' (test) { - test.expect(1); - this.plugin.host_list_regex=['test.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(true, r); - test.done(); - }, - 're hit' (test) { - test.expect(1); - this.plugin.host_list_regex=['.*est.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(true, r); - test.done(); - }, -} + describe('in_host_list', () => { + beforeEach(_set_up) -exports.hook_mail = { - setUp : _set_up, - 'null sender' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - } - this.connection.relaying=true; - this.plugin.hook_mail(next, this.connection, [new Address('<>')]); - }, - 'miss' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); - test.notEqual(-1, res.msg.indexOf('mail_from!local')); - test.done(); - }.bind(this); - this.plugin.host_list = { 'miss.com': true }; - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list = { 'example.com': true }; - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit, regex, exact' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list_regex = ['example.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit, regex, pattern' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list_regex = ['.*mple.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, -} + it('miss', () => { + assert.equal(false, this.plugin.in_host_list('test.com')); + }) + + it('hit', () => { + this.plugin.host_list['test.com'] = true; + assert.equal(true, this.plugin.in_host_list('test.com')); + }) + }) + + describe('in_host_regex', () => { + beforeEach(_set_up) + + it('undef', () => { + const r = this.plugin.in_host_regex('test.com'); + assert.equal(false, r); + }) + + it('miss', () => { + this.plugin.host_list_regex=['miss.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + const r = this.plugin.in_host_regex('test.com'); + assert.equal(false, r); + }) + + it('exact hit', () => { + this.plugin.host_list_regex=['test.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + const r = this.plugin.in_host_regex('test.com'); + assert.equal(true, r); + }) + + it('re hit', () => { + this.plugin.host_list_regex=['.*est.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + const r = this.plugin.in_host_regex('test.com'); + assert.equal(true, r); + }) + }) + + describe('hook_mail', () => { + beforeEach(_set_up) + + it('null sender', (done) => { + this.connection.relaying=true; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('<>')]); + }) + + it('miss', (done) => { + this.plugin.host_list = { 'miss.com': true }; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); + assert.notEqual(-1, res.msg.indexOf('mail_from!local')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit', (done) => { + this.plugin.host_list = { 'example.com': true }; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit, regex, exact', (done) => { + this.plugin.host_list_regex = ['example.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit, regex, pattern', (done) => { + this.plugin.host_list_regex = ['.*mple.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.host_list_base'); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + }) +}) diff --git a/tests/plugins/rcpt_to.in_host_list.js b/tests/plugins/rcpt_to.in_host_list.js index dcf3d8645..1d269a835 100644 --- a/tests/plugins/rcpt_to.in_host_list.js +++ b/tests/plugins/rcpt_to.in_host_list.js @@ -1,9 +1,10 @@ 'use strict'; +const assert = require('node:assert') const { Address } = require('address-rfc2821'); const fixtures = require('haraka-test-fixtures'); -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('rcpt_to.in_host_list'); this.plugin.inherits('rcpt_to.host_list_base'); @@ -19,200 +20,174 @@ function _set_up (done) { done(); } -exports.in_host_list = { - setUp : _set_up, - 'miss' (test) { - test.expect(1); - const r = this.plugin.in_host_list('test.com'); - test.equal(false, r); - test.done(); - }, - 'hit' (test) { - test.expect(1); +describe('in_host_list', () => { + beforeEach(_set_up) + + it('miss', () => { + assert.equal(this.plugin.in_host_list('test.com'), false); + }) + + it('hit', () => { this.plugin.host_list['test.com'] = true; - const r = this.plugin.in_host_list('test.com'); - test.equal(true, r); - test.done(); - }, -} + assert.equal(this.plugin.in_host_list('test.com'), true); + }) -exports.in_host_regex = { - setUp : _set_up, - 'undef' (test) { - test.expect(1); - const r = this.plugin.in_host_regex('test.com'); - test.equal(false, r); - test.done(); - }, - 'miss' (test) { - test.expect(1); - this.plugin.host_list_regex=['miss.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(false, r); - test.done(); - }, - 'exact hit' (test) { - test.expect(1); - this.plugin.host_list_regex=['test.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(true, r); - test.done(); - }, - 're hit' (test) { - test.expect(1); - this.plugin.host_list_regex=['.*est.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - const r = this.plugin.in_host_regex('test.com'); - test.equal(true, r); - test.done(); - }, -} + describe('in_host_regex', () => { + beforeEach(_set_up) -exports.hook_mail = { - setUp : _set_up, - 'null sender' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - } - this.connection.relaying=true; - this.plugin.hook_mail(next, this.connection, [new Address('<>')]); - }, - 'miss' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); - test.notEqual(-1, res.msg.indexOf('mail_from!local')); - test.done(); - }.bind(this); - this.plugin.host_list = { 'miss.com': true }; - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); - // console.log(res); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list = { 'example.com': true }; - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit, regex, exact' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); - // console.log(res); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list_regex = ['example.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, - 'hit, regex, pattern' (test) { - test.expect(3); - const next = function (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); - // console.log(res); - test.notEqual(-1, res.pass.indexOf('mail_from')); - test.done(); - }.bind(this); - this.plugin.host_list_regex = ['.*mple.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_mail(next, this.connection, [new Address('')]); - }, -} + it('undef', () => { + assert.equal(this.plugin.in_host_regex('test.com'), false); + }) -exports.hook_rcpt = { - setUp : _set_up, - 'missing txn' (test) { - test.expect(1); - // sometimes txn goes away, make sure it's handled - function next (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - } - delete this.connection.transaction; - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - test.ok(true); - test.done(); - }, - 'hit list' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(OK, rc); - test.equal(undefined, msg); - test.done(); - } - this.plugin.host_list = { 'test.com': true }; - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, - 'miss list' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - } - this.plugin.host_list = { 'miss.com': true }; - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, - 'hit regex, exact' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(OK, rc); - test.equal(undefined, msg); - test.done(); - } - this.plugin.host_list_regex=['test.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, - 'hit regex, pattern' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(OK, rc); - test.equal(undefined, msg); - test.done(); - } - this.plugin.host_list_regex=['.est.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, - 'miss regex, pattern' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(undefined, rc); - test.equal(undefined, msg); - test.done(); - } - this.plugin.host_list_regex=['a.est.com']; - this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, - 'rcpt miss, relaying to local sender' (test) { - test.expect(2); - function next (rc, msg) { - test.equal(OK, rc); - test.equal(undefined, msg); - test.done(); - } - this.connection.relaying=true; - this.connection.transaction.notes = { local_sender: true }; - this.plugin.hook_rcpt(next, this.connection, [new Address('test@test.com')]); - }, -} + it('miss', () => { + this.plugin.host_list_regex=['miss.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + assert.equal(this.plugin.in_host_regex('test.com'), false); + }) + + it('exact hit', () => { + this.plugin.host_list_regex=['test.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + assert.equal(this.plugin.in_host_regex('test.com'), true); + }) + + it('re hit', () => { + this.plugin.host_list_regex=['.*est.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + assert.equal(this.plugin.in_host_regex('test.com'), true); + }) + }) + + describe('hook_mail', () => { + beforeEach(_set_up) + + it('null sender', (done) => { + this.connection.relaying=true; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('<>')]); + }) + + it('miss', (done) => { + this.plugin.host_list = { 'miss.com': true }; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); + assert.notEqual(-1, res.msg.indexOf('mail_from!local')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit', (done) => { + this.plugin.host_list = { 'example.com': true }; + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit, regex, exact', (done) => { + this.plugin.host_list_regex = ['example.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + + it('hit, regex, pattern', (done) => { + this.plugin.host_list_regex = ['.*mple.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_mail((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + const res = this.connection.transaction.results.get('rcpt_to.in_host_list'); + // console.log(res); + assert.notEqual(-1, res.pass.indexOf('mail_from')); + done(); + }, this.connection, [new Address('')]); + }) + }) + + describe('hook_rcpt', () => { + beforeEach(_set_up) + + it('missing txn', (done) => { + // sometimes txn goes away, make sure it's handled + delete this.connection.transaction; + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + }, this.connection, [new Address('test@test.com')]); + assert.ok(true); + done(); + }) + + it('hit list', (done) => { + this.plugin.host_list = { 'test.com': true }; + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(OK, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + + it('miss list', (done) => { + this.plugin.host_list = { 'miss.com': true }; + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + + it('hit regex, exact', (done) => { + this.plugin.host_list_regex=['test.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(OK, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + + it('hit regex, pattern', (done) => { + this.plugin.host_list_regex=['.est.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(OK, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + + it('miss regex, pattern', (done) => { + this.plugin.host_list_regex=['a.est.com']; + this.plugin.hl_re = new RegExp (`^(?:${this.plugin.host_list_regex.join('|')})$`, 'i'); + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(undefined, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + + it('rcpt miss, relaying to local sender', (done) => { + this.connection.relaying=true; + this.connection.transaction.notes = { local_sender: true }; + this.plugin.hook_rcpt((rc, msg) => { + assert.equal(OK, rc); + assert.equal(undefined, msg); + done(); + }, this.connection, [new Address('test@test.com')]); + }) + }) +}) diff --git a/tests/plugins/relay.js b/tests/plugins/relay.js index 2ff3319d5..4c62b7a06 100644 --- a/tests/plugins/relay.js +++ b/tests/plugins/relay.js @@ -1,339 +1,303 @@ 'use strict'; +const assert = require('node:assert') -const fixtures = require('haraka-test-fixtures'); - -function _set_up (done) { +const fixtures = require('haraka-test-fixtures'); +const _set_up = (done) => { this.plugin = new fixtures.plugin('relay'); this.plugin.cfg = {}; this.connection = fixtures.connection.createConnection(); - done(); } -exports.plugin = { - setUp : _set_up, - 'should have register function' (test) { - test.expect(2); - test.ok(this.plugin); - test.equal('function', typeof this.plugin.register); - test.done(); - }, - 'register function should call register_hook()' (test) { - test.expect(1); - // console.log(this.plugin); - this.plugin.register(); - test.ok(this.plugin.register_hook.called); - // console.log(this.plugin); - test.done(); - }, -} +describe('relay', () => { + describe('plugin', () => { + beforeEach(_set_up) -exports.load_config_files = { - setUp : _set_up, - 'relay.ini' (test) { - test.expect(3); - this.plugin.load_relay_ini(); - test.ok(typeof this.plugin.cfg === 'object'); - test.ok(this.plugin.cfg); - test.ok(this.plugin.cfg.relay); - test.done(); - }, - 'relay_dest_domains.ini' (test) { - test.expect(1); - this.plugin.load_dest_domains(); - test.ok(typeof this.plugin.dest === 'object'); - test.done(); - }, -} + it('should have register function', () => { + assert.ok(this.plugin); + assert.equal('function', typeof this.plugin.register); + }) -exports.is_acl_allowed = { - setUp : _set_up, - 'bare IP' (test) { - test.expect(3); - this.plugin.acl_allow=['127.0.0.6']; - this.connection.remote.ip='127.0.0.6'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.0.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.1.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - test.done(); - }, - 'netmask' (test) { - test.expect(3); - this.plugin.acl_allow=['127.0.0.6/24']; - this.connection.remote.ip='127.0.0.6'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.0.5'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - this.connection.remote.ip='127.0.1.5'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - test.done(); - }, - 'mixed (ipv4 & ipv6 (Issue #428))' (test) { - test.expect(3); - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - - this.plugin.acl_allow=['2607:f060:b008:feed::2/64']; - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(true, this.plugin.is_acl_allowed(this.connection)); - - this.plugin.acl_allow=['127.0.0.6/24']; - this.connection.remote.ip='2607:f060:b008:feed::2'; - test.equal(false, this.plugin.is_acl_allowed(this.connection)); - - test.done(); - }, -} + it('register function should call register_hook()', () => { + this.plugin.register(); + assert.ok(this.plugin.register_hook.called); + }) + }) -exports.acl = { - setUp (callback) { - this.plugin = new fixtures.plugin('relay'); - this.plugin.cfg = { relay: { dest_domains: true } }; - this.connection = fixtures.connection.createConnection(); - callback(); - }, - 'relay.acl=false' (test) { - test.expect(1); - function next (rc) { - test.equal(undefined, rc); - test.done(); - } - this.plugin.cfg.relay.acl=false; - this.plugin.acl(() => {}, this.connection); - this.plugin.pass_relaying(next, this.connection); - }, - 'relay.acl=true, miss' (test) { - test.expect(2); - const next = function (rc) { - test.equal(undefined, rc); - test.equal(false, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.cfg.relay.acl=true; - this.plugin.acl(() => {}, this.connection); - this.plugin.pass_relaying(next, this.connection); - }, - 'relay.acl=true, hit' (test) { - test.expect(2); - const next = function (rc) { - test.equal(OK, rc); - test.equal(true, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.cfg.relay.acl=true; - this.connection.remote.ip='1.1.1.1'; - this.plugin.acl_allow=['1.1.1.1/32']; - this.plugin.acl(() => {}, this.connection); - this.plugin.pass_relaying(next, this.connection); - }, - 'relay.acl=true, hit, missing mask' (test) { - test.expect(2); - const next = function (rc) { - test.equal(OK, rc); - test.equal(true, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.cfg.relay.acl=true; - this.connection.remote.ip='1.1.1.1'; - this.plugin.acl_allow=['1.1.1.1']; - this.plugin.acl(() => {}, this.connection); - this.plugin.pass_relaying(next, this.connection); - }, - 'relay.acl=true, hit, net' (test) { - test.expect(2); - const next = function (rc) { - test.equal(OK, rc); - test.equal(true, this.connection.relaying); - test.done(); - }.bind(this); - this.plugin.cfg.relay.acl=true; - this.connection.remote.ip='1.1.1.1'; - this.plugin.acl_allow=['1.1.1.1/24']; - this.plugin.acl(() => {}, this.connection); - this.plugin.pass_relaying(next, this.connection); - }, -} + describe('load_config_files', () => { + beforeEach(_set_up) -exports.dest_domains = { - setUp (callback) { - this.plugin = new fixtures.plugin('relay'); - this.plugin.cfg = { relay: { dest_domains: true } }; - - this.connection = fixtures.connection.createConnection(); - this.connection.transaction = { - results: new fixtures.results(this.connection), - }; - - callback(); - }, - 'relay.dest_domains=false' (test) { - test.expect(1); - function next (rc) { - test.equal(undefined, rc); - test.done(); - } - this.plugin.cfg.relay.dest_domains=false; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'relaying' (test) { - test.expect(2); - const next = function (rc) { - test.equal(undefined, rc); - test.equal(1, this.connection.transaction.results.get('relay').skip.length); - test.done(); - }.bind(this); - this.connection.relaying=true; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'no config' (test) { - test.expect(2); - const next = function (rc) { - test.equal(undefined, rc); - test.equal(1, this.connection.transaction.results.get('relay').err.length); - test.done(); - }.bind(this); - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=undef' (test) { - test.expect(2); - const next = function (rc) { - test.equal(DENY, rc); - test.equal(1, this.connection.transaction.results.get('relay').fail.length); - test.done(); - }.bind(this); - this.plugin.dest = { domains: { foo: '{"action":"dunno"}' } }; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=deny' (test) { - test.expect(2); - const next = function (rc) { - test.equal(DENY, rc); - test.equal(1, this.connection.transaction.results.get('relay').fail.length); - test.done(); - }.bind(this); - this.plugin.dest = { domains: { foo: '{"action":"deny"}' } }; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=continue' (test) { - test.expect(2); - const next = function (rc) { - test.equal(CONT, rc); - test.equal(1, this.connection.transaction.results.get('relay').pass.length); - test.done(); - }.bind(this); - this.plugin.dest = { domains: { foo: '{"action":"continue"}' } }; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, - 'action=accept' (test) { - test.expect(2); - const next = function (rc) { - test.equal(CONT, rc); - test.equal(1, this.connection.transaction.results.get('relay').pass.length); - test.done(); - }.bind(this); - this.plugin.dest = { domains: { foo: '{"action":"continue"}' } }; - this.plugin.dest_domains(next, this.connection, [{host:'foo'}]); - }, -} + it('relay.ini', () => { + this.plugin.load_relay_ini(); + assert.ok(typeof this.plugin.cfg === 'object'); + assert.ok(this.plugin.cfg); + assert.ok(this.plugin.cfg.relay); + }) -exports.force_routing = { - setUp (callback) { - this.plugin = new fixtures.plugin('relay'); - this.plugin.cfg = { relay: { force_routing: true } }; - this.plugin.dest = {}; - - this.connection = fixtures.connection.createConnection(); - this.connection.transaction = { - results: new fixtures.results(this.connection), - }; - - callback(); - }, - 'relay.force_routing=false' (test) { - test.expect(1); - function next (rc) { - test.equal(undefined, rc); - test.done(); - } - this.plugin.cfg.relay.force_routing=false; - this.plugin.force_routing(next, this.connection, 'foo'); - }, - 'dest_domains empty' (test) { - test.expect(1); - function next (rc) { - test.equal(undefined, rc); - test.done(); - } - this.plugin.force_routing(next, this.connection, 'foo'); - }, - 'dest_domains, no route' (test) { - test.expect(2); - function next (rc, nexthop) { - // console.log(arguments); - test.equal(undefined, rc); - test.equal(undefined, nexthop); - test.done(); - } - this.plugin.dest = { domains: { foo: '{"action":"blah blah"}' } }; - this.plugin.force_routing(next, this.connection, 'foo'); - }, - 'dest_domains, route' (test) { - test.expect(2); - function next (rc, nexthop) { - test.equal(OK, rc); - test.equal('other-server', nexthop); - test.done(); - } - this.plugin.dest = { domains: { foo: '{"action":"blah blah","nexthop":"other-server"}' } }; - this.plugin.force_routing(next, this.connection, 'foo'); - }, - 'dest-domains, any' (test) { - test.expect(2); - function next (rc, nexthop) { - test.equal(OK, rc); - test.equal('any-server', nexthop); - test.done(); - } - this.plugin.dest = { domains: { foo: '{"action":"blah blah","nexthop":"other-server"}', - any: '{"action":"blah blah","nexthop":"any-server"}'} }; - this.plugin.force_routing(next, this.connection, 'not'); - } -} + it('relay_dest_domains.ini', () => { + this.plugin.load_dest_domains(); + assert.ok(typeof this.plugin.dest === 'object'); + }) + }) -exports.all = { - setUp : _set_up, - 'register_hook() should register available function' (test) { - test.expect(3); - test.ok(this.plugin.all); - test.equal('function', typeof this.plugin.all); - this.plugin.register(); - this.plugin.cfg.relay.all = true; - this.plugin.register_hook('rcpt', 'all'); // register() doesn't b/c config is disabled - // console.log(this.plugin.register_hook.args); - console.log(this.plugin.register_hook.args); - test.equals(this.plugin.register_hook.args[3][1], 'all'); - test.done(); - }, - 'all hook always returns OK' (test) { - function next (action) { - test.expect(1); - test.equals(action, OK); - test.done(); - } - this.plugin.cfg.relay = { all: true }; - this.plugin.all(next, this.connection, ['foo@bar.com']); - }, - 'all hook always sets connection.relaying to 1' (test) { - const next = function (action) { - test.expect(1); - test.equals(this.connection.relaying, 1); - test.done(); - }.bind(this); - - this.plugin.cfg.relay = { all: true }; - this.plugin.all(next, this.connection, ['foo@bar.com']); - } -} + describe('is_acl_allowed', () => { + beforeEach(_set_up) + + it('bare IP', () => { + this.plugin.acl_allow=['127.0.0.6']; + this.connection.remote.ip='127.0.0.6'; + assert.equal(true, this.plugin.is_acl_allowed(this.connection)); + this.connection.remote.ip='127.0.0.5'; + assert.equal(false, this.plugin.is_acl_allowed(this.connection)); + this.connection.remote.ip='127.0.1.5'; + assert.equal(false, this.plugin.is_acl_allowed(this.connection)); + }) + + it('netmask', () => { + this.plugin.acl_allow=['127.0.0.6/24']; + this.connection.remote.ip='127.0.0.6'; + assert.equal(true, this.plugin.is_acl_allowed(this.connection)); + this.connection.remote.ip='127.0.0.5'; + assert.equal(true, this.plugin.is_acl_allowed(this.connection)); + this.connection.remote.ip='127.0.1.5'; + assert.equal(false, this.plugin.is_acl_allowed(this.connection)); + }) + + it('mixed (ipv4 & ipv6 (Issue #428))', () => { + this.connection.remote.ip='2607:f060:b008:feed::2'; + assert.equal(false, this.plugin.is_acl_allowed(this.connection)); + + this.plugin.acl_allow=['2607:f060:b008:feed::2/64']; + this.connection.remote.ip='2607:f060:b008:feed::2'; + assert.equal(true, this.plugin.is_acl_allowed(this.connection)); + + this.plugin.acl_allow=['127.0.0.6/24']; + this.connection.remote.ip='2607:f060:b008:feed::2'; + assert.equal(false, this.plugin.is_acl_allowed(this.connection)); + }) + }) + + describe('acl', () => { + beforeEach((done) => { + this.plugin = new fixtures.plugin('relay'); + this.plugin.cfg = { relay: { dest_domains: true } }; + this.connection = fixtures.connection.createConnection(); + done(); + }) + + it('relay.acl=false', (done) => { + this.plugin.cfg.relay.acl=false; + this.plugin.acl(() => {}, this.connection); + this.plugin.pass_relaying((rc) => { + assert.equal(undefined, rc); + done(); + }, this.connection); + }) + + it('relay.acl=true, miss', (done) => { + this.plugin.cfg.relay.acl=true; + this.plugin.acl(() => {}, this.connection); + this.plugin.pass_relaying((rc) => { + assert.equal(undefined, rc); + assert.equal(false, this.connection.relaying); + done(); + }, this.connection); + }) + + it('relay.acl=true, hit', (done) => { + this.plugin.cfg.relay.acl=true; + this.connection.remote.ip='1.1.1.1'; + this.plugin.acl_allow=['1.1.1.1/32']; + this.plugin.acl(() => {}, this.connection); + this.plugin.pass_relaying((rc) => { + assert.equal(OK, rc); + assert.equal(true, this.connection.relaying); + done(); + }, this.connection); + }) + + it('relay.acl=true, hit, missing mask', (done) => { + this.plugin.cfg.relay.acl=true; + this.connection.remote.ip='1.1.1.1'; + this.plugin.acl_allow=['1.1.1.1']; + this.plugin.acl(() => {}, this.connection); + this.plugin.pass_relaying((rc) => { + assert.equal(OK, rc); + assert.equal(true, this.connection.relaying); + done(); + }, this.connection); + }) + + it('relay.acl=true, hit, net', (done) => { + this.plugin.cfg.relay.acl=true; + this.connection.remote.ip='1.1.1.1'; + this.plugin.acl_allow=['1.1.1.1/24']; + this.plugin.acl(() => {}, this.connection); + this.plugin.pass_relaying((rc) => { + assert.equal(OK, rc); + assert.equal(true, this.connection.relaying); + done(); + }, this.connection); + }) + }) + + describe('dest_domains', () => { + beforeEach((done) => { + this.plugin = new fixtures.plugin('relay'); + this.plugin.cfg = { relay: { dest_domains: true } }; + + this.connection = fixtures.connection.createConnection(); + this.connection.init_transaction(); + + done(); + }) + + it('relay.dest_domains=false', (done) => { + this.plugin.cfg.relay.dest_domains=false; + this.plugin.dest_domains((rc) => { + assert.equal(undefined, rc); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('relaying', (done) => { + this.connection.relaying=true; + this.plugin.dest_domains((rc) => { + assert.equal(undefined, rc); + assert.equal(1, this.connection.transaction.results.get('relay').skip.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('no config', (done) => { + this.plugin.dest_domains((rc) => { + assert.equal(undefined, rc); + assert.equal(1, this.connection.transaction.results.get('relay').err.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('action=undef', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"dunno"}' } }; + this.plugin.dest_domains((rc) => { + assert.equal(DENY, rc); + assert.equal(1, this.connection.transaction.results.get('relay').fail.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('action=deny', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"deny"}' } }; + this.plugin.dest_domains((rc) => { + assert.equal(DENY, rc); + assert.equal(1, this.connection.transaction.results.get('relay').fail.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('action=continue', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"continue"}' } }; + this.plugin.dest_domains((rc) => { + assert.equal(CONT, rc); + assert.equal(1, this.connection.transaction.results.get('relay').pass.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + + it('action=accept', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"continue"}' } }; + this.plugin.dest_domains((rc) => { + assert.equal(CONT, rc); + assert.equal(1, this.connection.transaction.results.get('relay').pass.length); + done(); + }, this.connection, [{host:'foo'}]); + }) + }) + + describe('force_routing', () => { + beforeEach((done) => { + this.plugin = new fixtures.plugin('relay'); + this.plugin.cfg = { relay: { force_routing: true } }; + this.plugin.dest = {}; + + this.connection = fixtures.connection.createConnection(); + this.connection.init_transaction() + + done(); + }) + + it('relay.force_routing=false', (done) => { + this.plugin.cfg.relay.force_routing=false; + this.plugin.force_routing((rc) => { + assert.equal(undefined, rc); + done(); + }, this.connection, 'foo'); + }) + + it('dest_domains empty', (done) => { + this.plugin.force_routing((rc) => { + assert.equal(undefined, rc); + done(); + }, this.connection, 'foo'); + }) + + it('dest_domains, no route', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"blah blah"}' } }; + this.plugin.force_routing((rc, nexthop) => { + assert.equal(undefined, rc); + assert.equal(undefined, nexthop); + done(); + }, this.connection, 'foo'); + }) + + it('dest_domains, route', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"blah blah","nexthop":"other-server"}' } }; + this.plugin.force_routing((rc, nexthop) => { + assert.equal(OK, rc); + assert.equal('other-server', nexthop); + done(); + }, this.connection, 'foo'); + }) + + it('dest-domains, any', (done) => { + this.plugin.dest = { domains: { foo: '{"action":"blah blah","nexthop":"other-server"}', + any: '{"action":"blah blah","nexthop":"any-server"}'} }; + this.plugin.force_routing((rc, nexthop) => { + assert.equal(OK, rc); + assert.equal('any-server', nexthop); + done(); + }, this.connection, 'not'); + }) + }) + + describe('all', () => { + beforeEach(_set_up) + + it('register_hook() should register available function', () => { + assert.ok(this.plugin.all); + assert.equal('function', typeof this.plugin.all); + this.plugin.register(); + this.plugin.cfg.relay.all = true; + this.plugin.register_hook('rcpt', 'all'); // register() doesn't b/c config is disabled + // console.log(this.plugin.register_hook.args); + assert.equal(this.plugin.register_hook.args[3][1], 'all'); + }) + + it('all hook always returns OK', (done) => { + this.plugin.cfg.relay = { all: true }; + this.plugin.all((action) => { + assert.equal(action, OK); + done(); + }, this.connection, ['foo@bar.com']); + }) + + it('all hook always sets connection.relaying to 1', (done) => { + this.plugin.cfg.relay = { all: true }; + this.plugin.all((action) => { + assert.equal(this.connection.relaying, 1); + done(); + }, this.connection, ['foo@bar.com']); + }) + }) +}) \ No newline at end of file diff --git a/tests/plugins/spamassassin.js b/tests/plugins/spamassassin.js index 8cea9a3e6..ff3191230 100644 --- a/tests/plugins/spamassassin.js +++ b/tests/plugins/spamassassin.js @@ -1,9 +1,10 @@ 'use strict'; +const assert = require('node:assert') const Address = require('address-rfc2821'); const fixtures = require('haraka-test-fixtures'); -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('spamassassin'); this.plugin.cfg = { @@ -19,153 +20,135 @@ function _set_up (done) { done(); } -exports.register = { - setUp : _set_up, - 'loads the spamassassin plugin' (test) { - test.expect(1); - test.equal('spamassassin', this.plugin.name); - test.done(); - }, - 'register loads spamassassin.ini' (test) { - test.expect(2); - this.plugin.register(); - test.ok(this.plugin.cfg); - test.ok(this.plugin.cfg.main.spamd_socket); - test.done(); - }, -} +describe('spamassassin', () => { + beforeEach(_set_up) -exports.load_spamassassin_ini = { - setUp : _set_up, - 'loads spamassassin.ini' (test) { - test.expect(3); - test.equal(undefined, this.plugin.cfg.main.spamd_socket); - this.plugin.load_spamassassin_ini(); - test.ok(this.plugin.cfg.main.spamd_socket); - test.equal(this.plugin.cfg.main.spamc_auth_header, 'X-Haraka-Relay'); - test.done(); - }, -} + describe('register', () => { -exports.should_skip = { - setUp : _set_up, - 'max_size not set' (test) { - // this.plugin.cfg = { main: {}} - test.expect(1); - test.equal(false, this.plugin.should_skip(this.connection)); - test.done(); - }, - 'max_size 10, data_bytes 9 = false' (test) { - test.expect(1); - this.plugin.cfg.main = { max_size: 10 }; - this.connection.transaction.data_bytes = 9; - test.equal(false, this.plugin.should_skip(this.connection)); - test.done(); - }, - 'max_size 10, data_bytes 11 = true' (test) { - test.expect(1); - this.plugin.cfg.main = { max_size: 10 }; - this.connection.transaction.data_bytes = 11; - test.equal(true, this.plugin.should_skip(this.connection)); - test.done(); - }, -} + it('loads the spamassassin plugin', () => { + assert.equal('spamassassin', this.plugin.name); + }) -// console.log(this.plugin.cfg); - -exports.get_spamd_headers = { - setUp : _set_up, - 'returns a spamd protocol request' (test) { - test.expect(1); - this.connection.transaction.mail_from = new Address.Address(''); - this.connection.transaction.uuid = 'THIS-IS-A-TEST-UUID'; - const headers = this.plugin.get_spamd_headers(this.connection, 'test_user'); - const expected_headers = [ - 'HEADERS SPAMC/1.4', - 'User: test_user', - '', - 'X-Envelope-From: matt@example.com', - 'X-Haraka-UUID: THIS-IS-A-TEST-UUID' - ]; - test.deepEqual(headers, expected_headers); - test.done(); - }, -} + it('register loads spamassassin.ini', () => { + this.plugin.register(); + assert.ok(this.plugin.cfg); + assert.ok(this.plugin.cfg.main.spamd_socket); + }) + }) -exports.get_spamd_headers_relaying = { - setUp : _set_up, - 'returns a spamd protocol request when relaying' (test) { - test.expect(1); - this.connection.transaction.mail_from = new Address.Address(''); - this.connection.transaction.uuid = 'THIS-IS-A-TEST-UUID'; - this.connection.set('relaying', true); - const headers = this.plugin.get_spamd_headers(this.connection, 'test_user'); - const expected_headers = [ - 'HEADERS SPAMC/1.4', - 'User: test_user', - '', - 'X-Envelope-From: matt@example.com', - 'X-Haraka-UUID: THIS-IS-A-TEST-UUID', - 'X-Haraka-Relaying123: true', - ]; - test.deepEqual(headers, expected_headers); - test.done(); - }, -} + describe('load_spamassassin_ini', () => { + beforeEach(_set_up) -exports.get_spamd_username = { - setUp : _set_up, - 'default' (test) { - test.expect(1); - test.equal('default', this.plugin.get_spamd_username(this.connection)); - test.done(); - }, - 'set in txn.notes.spamd_user' (test) { - test.expect(1); - this.connection.transaction.notes.spamd_user = 'txuser'; - test.equal('txuser', this.plugin.get_spamd_username(this.connection)); - test.done(); - }, - 'set in cfg.main.spamd_user' (test) { - test.expect(1); - this.plugin.cfg.main.spamd_user = 'cfguser'; - test.equal('cfguser', this.plugin.get_spamd_username(this.connection)); - test.done(); - }, - 'set to first-recipient' (test) { - this.plugin.cfg.main.spamd_user = 'first-recipient'; - this.connection.transaction.rcpt_to = [ new Address.Address('') ]; - test.equal('matt@example.com', this.plugin.get_spamd_username(this.connection)); - - test.done(); - }, -} + it('loads spamassassin.ini', () => { + assert.equal(undefined, this.plugin.cfg.main.spamd_socket); + this.plugin.load_spamassassin_ini(); + assert.ok(this.plugin.cfg.main.spamd_socket); + assert.equal(this.plugin.cfg.main.spamc_auth_header, 'X-Haraka-Relay'); + }) + }) -exports.score_too_high = { - setUp : _set_up, - 'no threshhold is not too high' (test) { - test.expect(1); - test.ok(!this.plugin.score_too_high(this.connection, {score: 5})); - test.done(); - }, - 'too high score is too high' (test) { - test.expect(1); - this.plugin.cfg.main.reject_threshold = 5; - test.equal('spam score exceeded threshold', this.plugin.score_too_high(this.connection, {score: 6})); - test.done(); - }, - 'ok score with relaying is ok' (test) { - test.expect(1); - this.connection.relaying = true; - this.plugin.cfg.main.relay_reject_threshold = 7; - test.equal('', this.plugin.score_too_high(this.connection, {score: 6})); - test.done(); - }, - 'too high score with relaying is too high' (test) { - test.expect(1); - this.connection.relaying = true; - this.plugin.cfg.main.relay_reject_threshold = 7; - test.equal('spam score exceeded relay threshold', this.plugin.score_too_high(this.connection, {score: 8})); - test.done(); - }, -} + describe('should_skip', () => { + + it('max_size not set', () => { + assert.equal(false, this.plugin.should_skip(this.connection)); + }) + + it('max_size 10, data_bytes 9 = false', () => { + this.plugin.cfg.main = { max_size: 10 }; + this.connection.transaction.data_bytes = 9; + assert.equal(false, this.plugin.should_skip(this.connection)); + }) + + it('max_size 10, data_bytes 11 = true', () => { + this.plugin.cfg.main = { max_size: 10 }; + this.connection.transaction.data_bytes = 11; + assert.equal(true, this.plugin.should_skip(this.connection)); + }) + }) + + describe('get_spamd_headers', () => { + + it('returns a spamd protocol request', () => { + this.connection.transaction.mail_from = new Address.Address(''); + this.connection.transaction.uuid = 'THIS-IS-A-TEST-UUID'; + const headers = this.plugin.get_spamd_headers(this.connection, 'test_user'); + const expected_headers = [ + 'HEADERS SPAMC/1.4', + 'User: test_user', + '', + 'X-Envelope-From: matt@example.com', + 'X-Haraka-UUID: THIS-IS-A-TEST-UUID' + ]; + assert.deepEqual(headers, expected_headers); + }) + }) + + describe('get_spamd_headers_relaying', () => { + beforeEach(_set_up) + + it('returns a spamd protocol request when relaying', () => { + this.connection.transaction.mail_from = new Address.Address(''); + this.connection.transaction.uuid = 'THIS-IS-A-TEST-UUID'; + this.connection.set('relaying', true); + const headers = this.plugin.get_spamd_headers(this.connection, 'test_user'); + const expected_headers = [ + 'HEADERS SPAMC/1.4', + 'User: test_user', + '', + 'X-Envelope-From: matt@example.com', + 'X-Haraka-UUID: THIS-IS-A-TEST-UUID', + 'X-Haraka-Relaying123: true', + ]; + assert.deepEqual(headers, expected_headers); + }) + }) + + describe('get_spamd_username', () => { + beforeEach(_set_up) + + it('default', () => { + assert.equal('default', this.plugin.get_spamd_username(this.connection)); + }) + + it('set in txn.notes.spamd_user', () => { + this.connection.transaction.notes.spamd_user = 'txuser'; + assert.equal('txuser', this.plugin.get_spamd_username(this.connection)); + }) + + it('set in cfg.main.spamd_user', () => { + this.plugin.cfg.main.spamd_user = 'cfguser'; + assert.equal('cfguser', this.plugin.get_spamd_username(this.connection)); + }) + + it('set to first-recipient', () => { + this.plugin.cfg.main.spamd_user = 'first-recipient'; + this.connection.transaction.rcpt_to = [ new Address.Address('') ]; + assert.equal('matt@example.com', this.plugin.get_spamd_username(this.connection)); + }) + }) + + describe('score_too_high', () => { + beforeEach(_set_up) + + it('no threshhold is not too high', () => { + assert.ok(!this.plugin.score_too_high(this.connection, {score: 5})); + }) + + it('too high score is too high', () => { + this.plugin.cfg.main.reject_threshold = 5; + assert.equal('spam score exceeded threshold', this.plugin.score_too_high(this.connection, {score: 6})); + }) + + it('ok score with relaying is ok', () => { + this.connection.relaying = true; + this.plugin.cfg.main.relay_reject_threshold = 7; + assert.equal('', this.plugin.score_too_high(this.connection, {score: 6})); + }) + + it('too high score with relaying is too high', () => { + this.connection.relaying = true; + this.plugin.cfg.main.relay_reject_threshold = 7; + assert.equal('spam score exceeded relay threshold', this.plugin.score_too_high(this.connection, {score: 8})); + }) + }) +}) diff --git a/tests/plugins/status.js b/tests/plugins/status.js index 04ebc87fa..128d519d4 100644 --- a/tests/plugins/status.js +++ b/tests/plugins/status.js @@ -1,12 +1,14 @@ 'use strict'; +const assert = require('node:assert') + const fixtures = require('haraka-test-fixtures'); const outbound = require('../../outbound'); const TimerQueue = require('../../outbound/timer_queue'); const Connection = fixtures.connection; -function _set_up (done) { +const _set_up = (done) => { this.plugin = new fixtures.plugin('status'); this.plugin.outbound = outbound; @@ -15,124 +17,114 @@ function _set_up (done) { done(); } -exports.register = { - setUp : _set_up, - 'loads the status plugin' (test) { - test.expect(1); - test.equal('status', this.plugin.name); - test.done(); - }, -} - -exports.access = { - setUp : _set_up, - 'remote' (test) { +describe('status', () => { - test.expect(1); - function cb (code) { - test.equal(DENY, code); - test.done(); - } + describe('register', () => { + beforeEach(_set_up) - this.connection.remote.is_local = false; + it('loads the status plugin', () => { + assert.equal('status', this.plugin.name); + }) + }) - this.plugin.hook_unrecognized_command(cb, this.connection, ['STATUS', 'POOL LIST']); - } -} + describe('access', () => { + beforeEach(_set_up) -exports.pools = { - setUp : _set_up, - 'list_pools' (test) { + it('remote', (done) => { + this.connection.remote.is_local = false; + this.plugin.hook_unrecognized_command((code) => { + assert.equal(DENY, code); + done(); + }, this.connection, ['STATUS', 'POOL LIST']); + }) + }) - test.expect(1); - this.connection.respond = (code, message) => { - const data = JSON.parse(message); - test.equal('object', typeof data); // there should be one pools array for noncluster and more for cluster - test.done(); - }; + describe('pools', () => { + beforeEach(_set_up) - this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'POOL LIST']); - } -} - -exports.queues = { - setUp : _set_up, - 'inspect_queue' (test) { - // should list delivery_queue and temp_fail_queue per cluster children - test.expect(2); - - outbound.temp_fail_queue = new TimerQueue(10); - outbound.temp_fail_queue.add('file1', 100, () => {}); - outbound.temp_fail_queue.add('file2', 100, () => {}); - - this.connection.respond = (code, message) => { - const data = JSON.parse(message); - test.equal(0, data.delivery_queue.length); - test.equal(2, data.temp_fail_queue.length); - test.done(); - }; - this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE INSPECT']); - }, - 'stat_queue' (test) { - // should list files only - test.expect(1); - - this.connection.respond = (code, message) => { - const data = JSON.parse(message); - test.ok(/^\d+\/\d+\/\d+$/.test(data)); - test.done(); - }; - this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE STATS']); - }, - 'list_queue' (test) { - // should list files only - test.expect(1); - - this.connection.respond = (code, message) => { - const data = JSON.parse(message); - test.equal(0, data.length); - test.done(); - }; - this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE LIST']); - }, - 'discard_from_queue' (test) { - const self = this; - - test.expect(1); - - outbound.temp_fail_queue = new TimerQueue(10); - outbound.temp_fail_queue.add('file1', 10, () => { - test.ok(false, 'This callback should not be called'); - test.done(); - }); - outbound.temp_fail_queue.add('file2', 2000, () => {}); - - function res () { - self.connection.respond = (code, message) => { + it('list_pools', (done) => { + this.connection.respond = (code, message) => { const data = JSON.parse(message); - test.equal(1, data.temp_fail_queue.length); - test.done(); + assert.equal('object', typeof data); // there should be one pools array for noncluster and more for cluster + done(); }; - self.plugin.hook_unrecognized_command(() => {}, self.connection, ['STATUS', 'QUEUE INSPECT']); - } + this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'POOL LIST']); + }) + }) - this.plugin.hook_unrecognized_command(res, this.connection, ['STATUS', 'QUEUE DISCARD file1']); - }, - 'push_email_at_queue' (test) { - test.expect(1); + describe('queues', () => { + beforeEach(_set_up) - const timeout = setTimeout(() => { - test.ok(false, 'Timeout'); - test.done(); - }, 1000); + it('inspect_queue', (done) => { + // should list delivery_queue and temp_fail_queue per cluster children + outbound.temp_fail_queue = new TimerQueue(10); + outbound.temp_fail_queue.add('file1', 100, () => {}); + outbound.temp_fail_queue.add('file2', 100, () => {}); - outbound.temp_fail_queue.add('file', 1500, () => { - clearTimeout(timeout); + this.connection.respond = (code, message) => { + const data = JSON.parse(message); + assert.equal(0, data.delivery_queue.length); + assert.equal(2, data.temp_fail_queue.length); + done(); + }; + this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE INSPECT']); + }) - test.ok(true); - test.done(); - }); + it('stat_queue', (done) => { + // should list files only + this.connection.respond = (code, message) => { + const data = JSON.parse(message); + assert.ok(/^\d+\/\d+\/\d+$/.test(data)); + done(); + }; + this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE STATS']); + }) - this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE PUSH file']); - }, -} + it('list_queue', (done) => { + // should list files only + this.connection.respond = (code, message) => { + const data = JSON.parse(message); + assert.equal(0, data.length); + done(); + }; + this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE LIST']); + }) + + it('discard_from_queue', (done) => { + const self = this; + + outbound.temp_fail_queue = new TimerQueue(10); + outbound.temp_fail_queue.add('file1', 10, () => { + assert.ok(false, 'This callback should not be called'); + done(); + }) + + outbound.temp_fail_queue.add('file2', 2000, () => {}); + + this.plugin.hook_unrecognized_command(() => { + self.connection.respond = (code, message) => { + const data = JSON.parse(message); + assert.equal(1, data.temp_fail_queue.length); + done(); + } + self.plugin.hook_unrecognized_command(() => {}, self.connection, ['STATUS', 'QUEUE INSPECT']); + }, this.connection, ['STATUS', 'QUEUE DISCARD file1']); + }) + + it('push_email_at_queue', (done) => { + const timeout = setTimeout(() => { + assert.ok(false, 'Timeout'); + done(); + }, 1000); + + outbound.temp_fail_queue.add('file', 1500, () => { + clearTimeout(timeout); + + assert.ok(true); + done(); + }); + + this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE PUSH file']); + }) + }) +}) \ No newline at end of file diff --git a/tests/plugins/tls.js b/tests/plugins/tls.js index 1174e0afc..7e6fd7635 100644 --- a/tests/plugins/tls.js +++ b/tests/plugins/tls.js @@ -1,84 +1,70 @@ 'use strict'; +const assert = require('node:assert') const path = require('path'); const fixtures = require('haraka-test-fixtures'); const Plugin = fixtures.plugin; -function _set_up (done) { - const plugin = new Plugin('tls'); - this.plugin = plugin; +const _set_up = (done) => { + this.plugin = new Plugin('tls') this.connection = new fixtures.connection.createConnection(); // use tests/config instead of ./config - plugin.config = plugin.config.module_config(path.resolve('tests')); - plugin.net_utils.config = plugin.net_utils.config.module_config(path.resolve('tests')); + this.plugin.config = this.plugin.config.module_config(path.resolve('tests')); + this.plugin.net_utils.config = this.plugin.net_utils.config.module_config(path.resolve('tests')); - plugin.tls_opts = {}; + this.plugin.tls_opts = {}; done(); } -exports.plugin = { - setUp : _set_up, - 'has function register' (test) { - test.expect(2); - test.ok(this.plugin); - test.equal('function', typeof this.plugin.register); - test.done(); - }, - 'has function upgrade_connection' (test) { - test.expect(1); - test.equal('function', typeof this.plugin.upgrade_connection); - test.done(); - }, - 'has function advertise_starttls' (test) { - test.expect(1); - test.equal('function', typeof this.plugin.advertise_starttls); - test.done(); - }, - 'has function emit_upgrade_msg' (test) { - test.expect(1); - test.equal('function', typeof this.plugin.emit_upgrade_msg); - test.done(); - }, -} +describe('tls', ()=> { + beforeEach(_set_up) -exports.register = { - setUp (done) { - this.plugin = new Plugin('tls'); - done(); - }, - 'with certs, should call register_hook()' (test) { - test.expect(1); - this.plugin.register(); - test.ok(this.plugin.register_hook.called); - // console.log(this.plugin); - test.done(); - }, -} + it('has function register', () => { + assert.ok(this.plugin); + assert.equal('function', typeof this.plugin.register); + }) -exports.emit_upgrade_msg = { - setUp : _set_up, - 'should emit a log message' (test) { - test.expect(1); - test.equal(this.plugin.emit_upgrade_msg(this.connection, true, '', { - subject: { - CN: 'TLS.subject', - O: 'TLS.org' - }, - }), - 'secured: verified=true cn="TLS.subject" organization="TLS.org"'); - test.done(); - }, - 'should emit a log message with error' (test) { - test.expect(1); - test.equal(this.plugin.emit_upgrade_msg(this.connection, true, 'oops', { - subject: { - CN: 'TLS.subject', - O: 'TLS.org' - }, - }), - 'secured: verified=true error="oops" cn="TLS.subject" organization="TLS.org"'); - test.done(); - } -} + it('has function upgrade_connection', () => { + assert.equal('function', typeof this.plugin.upgrade_connection); + }) + + it('has function advertise_starttls', () => { + assert.equal('function', typeof this.plugin.advertise_starttls); + }) + + it('has function emit_upgrade_msg', () => { + assert.equal('function', typeof this.plugin.emit_upgrade_msg); + }) + + describe('register', ()=> { + it('with certs, should call register_hook()', () => { + this.plugin.register(); + assert.ok(this.plugin.register_hook.called); + }) + }) + + describe('emit_upgrade_msg', ()=> { + + it('should emit a log message', () => { + assert.equal(this.plugin.emit_upgrade_msg(this.connection, true, '', { + subject: { + CN: 'TLS.subject', + O: 'TLS.org' + }, + }), + 'secured: verified=true cn="TLS.subject" organization="TLS.org"'); + }) + + it('should emit a log message with error', () => { + assert.equal(this.plugin.emit_upgrade_msg(this.connection, true, 'oops', { + subject: { + CN: 'TLS.subject', + O: 'TLS.org' + }, + }), + 'secured: verified=true error="oops" cn="TLS.subject" organization="TLS.org"'); + }) + }) +}) diff --git a/tests/rfc1869.js b/tests/rfc1869.js index 6126cb958..63f8b7794 100644 --- a/tests/rfc1869.js +++ b/tests/rfc1869.js @@ -1,61 +1,73 @@ +const assert = require('node:assert') + const { parse } = require('../rfc1869'); -function _check (test, line, expected) { - test.expect(1 + expected.length); +function _check (line, expected) { const match = /^(MAIL|RCPT)\s+(.*)$/.exec(line); const parsed = parse(match[1].toLowerCase(), match[2]); - test.equal(parsed.length, expected.length); + assert.equal(parsed.length, expected.length); for (let x = 0; x < expected.length; x++) { - test.equal(parsed[x], expected[x]); + assert.equal(parsed[x], expected[x]); } - test.done(); } -exports.basic = { - 'MAIL FROM:<>': test => { - _check(test, 'MAIL FROM:<>', ['<>']); - }, - 'MAIL FROM:': test => { - _check(test, 'MAIL FROM:', ['<>']); - }, - 'MAIL FROM:': test => { - _check(test, 'MAIL FROM:', ['']); - }, - 'MAIL FROM:user': test => { - _check(test, 'MAIL FROM:user', ['user']); - }, - 'MAIL FROM:user size=1234': test => { - _check(test, 'MAIL FROM:user size=1234', ['user', 'size=1234']); - }, - 'MAIL FROM:user@domain size=1234': test => { - _check(test, 'MAIL FROM:user@domain size=1234', +describe('rfc1869', () => { + it('MAIL FROM:<>', () => { + _check('MAIL FROM:<>', ['<>']); + }) + + it('MAIL FROM:', () => { + _check('MAIL FROM:', ['<>']); + }) + + it('MAIL FROM:', () => { + _check('MAIL FROM:', ['']); + }) + + it('MAIL FROM:user', () => { + _check('MAIL FROM:user', ['user']); + }) + + it('MAIL FROM:user size=1234', () => { + _check('MAIL FROM:user size=1234', ['user', 'size=1234']); + }) + + it('MAIL FROM:user@domain size=1234', () => { + _check('MAIL FROM:user@domain size=1234', ['user@domain', 'size=1234']); - }, - 'MAIL FROM: size=1234': test => { - _check(test, 'MAIL FROM: size=1234', + }) + + it('MAIL FROM: size=1234', () => { + _check('MAIL FROM: size=1234', ['', 'size=1234']); - }, - 'MAIL FROM: somekey': test => { - _check(test, 'MAIL FROM: somekey', + }) + + it('MAIL FROM: somekey', () => { + _check('MAIL FROM: somekey', ['', 'somekey']); - }, - 'MAIL FROM: somekey other=foo': test => { - _check(test, 'MAIL FROM: somekey other=foo', + }) + + it('MAIL FROM: somekey other=foo', () => { + _check('MAIL FROM: somekey other=foo', ['', 'somekey', 'other=foo']); - }, - 'RCPT TO ugly': test => { - _check(test, 'RCPT TO: 0@mailblog.biz 0=9 1=9', + }) + + it('RCPT TO ugly', () => { + _check('RCPT TO: 0@mailblog.biz 0=9 1=9', ['<0@mailblog.biz>', '0=9', '1=9']); - }, - 'RCPT TO: state=1': test => { - _check(test, 'RCPT TO: state=1', + }) + + it('RCPT TO: state=1', () => { + _check('RCPT TO: state=1', ['', 'state=1']); - }, - 'RCPT TO: foo=bar': test => { - _check(test, 'RCPT TO: foo=bar', + }) + + it('RCPT TO: foo=bar', () => { + _check('RCPT TO: foo=bar', ['', 'foo=bar']); - }, - 'RCPT TO:': test => { - _check(test, 'RCPT TO:', ['']); - }, -} + }) + + it('RCPT TO:', () => { + _check('RCPT TO:', ['']); + }) +}) diff --git a/tests/server.js b/tests/server.js index 02624dc54..a7f946ed8 100644 --- a/tests/server.js +++ b/tests/server.js @@ -1,190 +1,44 @@ +const assert = require('node:assert') + const path = require('path'); const endpoint = require('../endpoint'); const message = require('haraka-email-message') -function _set_up (done) { - +const _set_up = (done) => { this.config = require('haraka-config'); this.server = require('../server'); - done(); } -exports.get_listen_addrs = { - setUp : _set_up, - 'IPv4 fully qualified' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({listen: '127.0.0.1:25'}); - test.deepEqual(['127.0.0.1:25'], listeners); - test.done(); - }, - 'IPv4, default port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({listen: '127.0.0.1'}); - test.deepEqual(['127.0.0.1:25'], listeners); - test.done(); - }, - 'IPv4, custom port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({ - listen: '127.0.0.1' - }, 250); - test.deepEqual(['127.0.0.1:250'], listeners); - test.done(); - }, - 'IPv6 fully qualified' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({listen: '[::1]:25'}); - test.deepEqual(['[::1]:25'], listeners); - test.done(); - }, - 'IPv6, default port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({listen: '[::1]'}); - test.deepEqual(['[::1]:25'], listeners); - test.done(); - }, - 'IPv6, custom port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({listen: '[::1]'}, 250); - test.deepEqual(['[::1]:250'], listeners); - test.done(); - }, - 'IPv4 & IPv6 fully qualified' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({ - listen: '127.0.0.1:25,[::1]:25' - }); - test.deepEqual(['127.0.0.1:25','[::1]:25'], listeners); - test.done(); - }, - 'IPv4 & IPv6, default port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({ - listen: '127.0.0.1:25,[::1]' - }); - test.deepEqual(['127.0.0.1:25','[::1]:25'], listeners); - test.done(); - }, - 'IPv4 & IPv6, custom port' (test) { - test.expect(1); - const listeners = this.server.get_listen_addrs({ - listen: '127.0.0.1,[::1]' - }, 250); - test.deepEqual(['127.0.0.1:250','[::1]:250'], listeners); - test.done(); - }, -} - -exports.load_smtp_ini = { - setUp : _set_up, - 'saves settings to Server.cfg' (test) { - test.expect(3); - this.server.load_smtp_ini(); - // console.log(this.server.cfg); - const c = this.server.cfg.main; - test.notEqual(c.daemonize, undefined); - test.notEqual(c.daemon_log_file, undefined); - test.notEqual(c.daemon_pid_file, undefined); - test.done(); - } -} - -exports.get_smtp_server = { - setUp (done) { - this.config = require('haraka-config'); - this.config = this.config.module_config(path.resolve('tests')); - - this.server = require('../server'); - this.server.config = this.config.module_config(path.resolve('tests')); - this.server.plugins.config = this.config.module_config(path.resolve('tests')); - - this.server.load_default_tls_config(() => { - setTimeout(() => { - done(); - }, 200); - }); - }, - 'gets a net server object' (test) { - this.server.get_smtp_server(endpoint('0.0.0.0:2501'), 10, (server) => { - if (!server) { - console.error('unable to bind to 0.0.0.0:2501'); - // test.expect(0); - if (process.env.CI) { // can't bind to IP/port (fails on Travis) - test.done(); - return; - } - } - test.expect(3); - test.ok(server); - test.equal(server.has_tls, false); - server.getConnections((err, count) => { - test.equal(0, count); - test.done(); - }); - }); - }, - 'gets a TLS net server object' (test) { - this.server.cfg.main.smtps_port = 2502; - this.server.get_smtp_server(endpoint('0.0.0.0:2502'), 10, (server) => { - if (!server) { - console.error('unable to bind to 0.0.0.0:2502'); - // test.expect(0); - if (process.env.CI) { // can't bind to IP/port (fails on Travis) - test.done(); - return; - } - } - test.expect(3); - test.ok(server); - test.equal(server.has_tls, true); - server.getConnections((err, count) => { - test.equal(0, count); - test.done(); - }); - }); - } -} - -exports.get_http_docroot = { - setUp : _set_up, - 'gets a fs path' (test) { - test.expect(1); - const docroot = this.server.get_http_docroot(); - test.ok(docroot); - test.done(); - }, -} - -function _setupServer (test, ip_port, done) { +const _setupServer = (ip_port, done) => { process.env.YES_REALLY_DO_DISCARD=1; // for queue/discard plugin process.env.HARAKA_TEST_DIR=path.resolve('tests'); // test sets the default path for plugin instances to the test dir const test_cfg_path=path.resolve('tests'); - test.server = require('../server'); - test.config = require('haraka-config').module_config(test_cfg_path); - test.server.logger.loglevel = 6; // INFO + this.server = require('../server'); + this.config = require('haraka-config').module_config(test_cfg_path); + this.server.logger.loglevel = 6; // INFO // set the default path for the plugin loader - test.server.config = test.config.module_config(test_cfg_path); - test.server.plugins.config = test.config.module_config(test_cfg_path); - // test.server.outbound.config = test.config.module_config(test_cfg_path); - - test.server.load_smtp_ini(); - test.server.cfg.main.listen = ip_port; - test.server.cfg.main.smtps_port = 2465; - // console.log(test.server.cfg); - test.server.load_default_tls_config(() => { - test.server.createServer({}); + this.server.config = this.config.module_config(test_cfg_path); + this.server.plugins.config = this.config.module_config(test_cfg_path); + // this.server.outbound.config = this.config.module_config(this_cfg_path); + + this.server.load_smtp_ini(); + this.server.cfg.main.listen = ip_port; + this.server.cfg.main.smtps_port = 2465; + + this.server.load_default_tls_config(() => { + this.server.createServer({}); setTimeout(() => { done(); }, 200); }) } -function _tearDownServer (done) { +const _tearDownServer = (done) => { delete process.env.YES_REALLY_DO_DISCARD; delete process.env.HARAKA_TEST_DIR; this.server.stopListeners(); @@ -194,244 +48,208 @@ function _tearDownServer (done) { }, 200); } -exports.smtp_client = { - setUp (done) { - _setupServer(this, 'localhost:2500', done); - }, - tearDown: _tearDownServer, - 'accepts SMTP message': test => { +describe('server', () => { - test.expect(1); - const server = { notes: { } }; - const cfg = { - connect_timeout: 2, - } + describe('get_listen_addrs', () => { + beforeEach(_set_up) - const smtp_client = require('../smtp_client'); + it('IPv4 fully qualified', () => { + const listeners = this.server.get_listen_addrs({listen: '127.0.0.1:25'}); + assert.deepEqual(['127.0.0.1:25'], listeners); + }) - smtp_client.get_client(server, (client) => { + it('IPv4, default port', () => { + const listeners = this.server.get_listen_addrs({listen: '127.0.0.1'}); + assert.deepEqual(['127.0.0.1:25'], listeners); + }) - client - .on('greeting', command => { - client.send_command('HELO', 'haraka.local'); - }) - .on('helo', () => { - client.send_command('MAIL', 'FROM:'); - }) - .on('mail', () => { - client.send_command('RCPT', 'TO:'); - }) - .on('rcpt', () => { - client.send_command('DATA'); - }) - .on('data', () => { - const message_stream = new message.stream( - { main : { spool_after : 1024 } }, "theMessageId" - ); + it('IPv4, custom port', () => { + const listeners = this.server.get_listen_addrs({ listen: '127.0.0.1'}, 250); + assert.deepEqual(['127.0.0.1:250'], listeners); + }) - message_stream.on('end', () => { - client.socket.write('.\r\n'); - }) - message_stream.add_line('Header: test\r\n'); - message_stream.add_line('\r\n'); - message_stream.add_line('I am body text\r\n'); - message_stream.add_line_end(); + it('IPv6 fully qualified', () => { + const listeners = this.server.get_listen_addrs({listen: '[::1]:25'}); + assert.deepEqual(['[::1]:25'], listeners); + }) - client.start_data(message_stream); - }) - .on('dot', () => { - test.ok(1); - client.release(); - test.done(); + it('IPv6, default port', () => { + const listeners = this.server.get_listen_addrs({listen: '[::1]'}); + assert.deepEqual(['[::1]:25'], listeners); + }) + + it('IPv6, custom port', () => { + const listeners = this.server.get_listen_addrs({listen: '[::1]'}, 250); + assert.deepEqual(['[::1]:250'], listeners); + }) + + it('IPv4 & IPv6 fully qualified', () => { + const listeners = this.server.get_listen_addrs({ + listen: '127.0.0.1:25,[::1]:25' + }); + assert.deepEqual(['127.0.0.1:25','[::1]:25'], listeners); + }) + + it('IPv4 & IPv6, default port', () => { + const listeners = this.server.get_listen_addrs({ + listen: '127.0.0.1:25,[::1]' + }); + assert.deepEqual(['127.0.0.1:25','[::1]:25'], listeners); + }) + + it('IPv4 & IPv6, custom port', () => { + const listeners = this.server.get_listen_addrs({ + listen: '127.0.0.1,[::1]' + }, 250); + assert.deepEqual(['127.0.0.1:250','[::1]:250'], listeners); + }) + }) + + describe('load_smtp_ini', () => { + beforeEach(_set_up) + + it('saves settings to Server.cfg', () => { + this.server.load_smtp_ini(); + // console.log(this.server.cfg); + const c = this.server.cfg.main; + assert.notEqual(c.daemonize, undefined); + assert.notEqual(c.daemon_log_file, undefined); + assert.notEqual(c.daemon_pid_file, undefined); + }) + }) + + describe('get_smtp_server', () => { + beforeEach((done) => { + this.config = require('haraka-config'); + this.config = this.config.module_config(path.resolve('tests')); + + this.server = require('../server'); + this.server.config = this.config; + this.server.plugins.config = this.config; + + this.server.load_default_tls_config(() => { + setTimeout(() => { + done(); + }, 200); + }); + }) + + it('gets a net server object', (done) => { + this.server.get_smtp_server(endpoint('0.0.0.0:2501'), 10, (server) => { + if (!server) { + console.error('unable to bind to 0.0.0.0:2501'); + if (process.env.CI) return // can't bind to IP/port (fails on Travis) + } + assert.ok(server); + assert.equal(server.has_tls, false); + server.getConnections((err, count) => { + assert.equal(0, count); + done() }) - .on('bad_code', (code, msg) => { - client.release(); - test.done(); + }) + }) + + it('gets a TLS net server object', (done) => { + this.server.cfg.main.smtps_port = 2502; + this.server.get_smtp_server(endpoint('0.0.0.0:2502'), 10, (server) => { + if (!server) { + console.error('unable to bind to 0.0.0.0:2502'); + if (process.env.CI) return // can't bind to IP/port (fails on Travis) + } + assert.ok(server); + assert.equal(server.has_tls, true); + server.getConnections((err, count) => { + assert.equal(0, count); + done() }); + }); + }) + }) - }, { port: 2500, host: 'localhost', cfg }); - }, -} + describe('get_http_docroot', () => { + beforeEach(_set_up) -exports.nodemailer = { - setUp (done) { - _setupServer(this, '127.0.0.1:2503', done); - }, - tearDown: _tearDownServer, - 'accepts SMTP message': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2503, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false - } - }); - transporter.sendMail({ - from: '"Testalicious Matt" ', - to: 'nobody-will-see-this@haraka.local', - envelope: { - from: 'Haraka Test ', - to: 'Discard Queue ', - }, - subject: 'Hello ✔', - text: 'Hello world ?', - html: 'Hello world ?', - }, - (error, info) => { - if (error){ - console.log(error); - test.done(); - return; - } - test.deepEqual(info.accepted, [ 'discard@haraka.local' ]); - console.log(`Message sent: ${info.response}`); - test.done(); - }); - }, - 'accepts authenticated SMTP': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2503, - auth: { - user: 'matt', - pass: 'goodPass' - }, - requireTLS: true, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false - } - }); - transporter.sendMail({ - from: '"Testalicious Matt" ', - to: 'nobody-will-see-this@haraka.local', - envelope: { - from: 'Haraka Test ', - to: 'Discard Queue ', - }, - subject: 'Hello ✔', - text: 'Hello world ?', - html: 'Hello world ?', - }, - (error, info) => { - if (error){ - console.log(error); - test.done(); - return; - } - test.deepEqual(info.accepted, [ 'discard@haraka.local' ]); - console.log(`Message sent: ${info.response}`); - test.done(); - }); - }, - 'rejects invalid auth': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2503, - auth: { - user: 'matt', - pass: 'badPass' - }, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false - } - }); - transporter.sendMail({ - from: '"Testalicious Matt" ', - to: 'nobody-will-see-this@haraka.local', - envelope: { - from: 'Haraka Test ', - to: 'Discard Queue ', - }, - subject: 'Hello ✔', - text: 'Hello world ?', - html: 'Hello world ?', - }, - (error, info) => { - if (error){ - test.equals(error.code, 'EAUTH'); - // console.log(error); - test.done(); - return; - } - console.log(info.response); - test.done(); - }); - }, - 'DKIM validates signed message': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2503, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false - } - }); - transporter.sendMail({ - from: '"Testalicious Matt" ', - to: 'nobody-will-see-this@haraka.local', - envelope: { - from: 'Haraka Test ', - to: 'Discard Queue ', - }, - subject: 'Hello ✔', - text: 'Hello world ?', - html: 'Hello world ?', - dkim: { - domainName: "test.simerson.com", - keySelector: "harakatest2017", - privateKey: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxqoUAnQ9GB3iNnkS7coj0Iggd0nyryW062tpK95NC5UXmmAwIpUMfkYdiHY2o2duWYGF0Bp237M/QXKhJYTXfsgkwP/bq9OGWtRZxHPHhbhdjbiI\nqObi6zvYcxrI77gpWDDvruhMeS9Hwa1R99pLUWd4PsuYTzbV/jwu2pz+XZXXXNEU\nVxzDAAj0yF7mwxHMLzQfR+hdhWcrgN0stUP0o7hm7hoOP8IWgcSW3JiQYavIKoI4\nm4+I9I1LzDJN2rHVnQvmjUrqqpG7X6SyFVFtuTWGaMqf1Cj/t8eSvU9VdgLFllS8\ntThqUZHq5S5hm8M8VzLuQLG9U0dtFolcFmJkbQIDAQABAoIBAB4fUbNhjpXmihM6\nXm1htfZ7fXi45Kw76me7vJGjPklgTNjidsn3kZJf7UBwtC4ok6nMos6ABMA8fH3e\n9KIst0QI8tG0ucke5INHKWlJKNqUrtK7RTVe9M84HsStLgRzBwnRObZqkJXbXmT2\nc7RCDCOGrcvPsQNpzB6lX3FUVpk3x24RXpQV1qSgH8yuHSPc1C6rssXwPAgnESfS\nK3MHRx2CLZvTTkq/YCsT+wS/O9RWPCVOYuWaa5DDDAIp3Yw1wYq9Upoh0BdIFC3U\nWm+5Cr3o9wxcvS6+W2RA6I51eymzvCU5ZakWt/bnUDb6/ByxsWOn5rL4WfPpCwE4\nnuC72v0CgYEA9imEq6a0GoaEsMoR7cxT7uXKimQH+Jaq3CGkuh0iN32F4FXhuUKz\nLYKSLCZzpb1MiDJv6BBchV6uSQ6ATo1cZ8WzYQISikk175bf0SPom591OZElvKA2\nSOrTrXtbl33YbWZEgyEcpTgelVi5ys9rj4eKkMvM0lwRmW6gctEFXRcCgYEAzpqc\nR/wqPjgPhpF1CZtdEwOZg4kkOig8CBcuQ7o/hDG7N69A9ZbeJO8eD+gKDrHRfkYr\nTH/UdkZGjilBk/lxnpIZpyBLxQ6UdhNPuwtxXKAvuSN+aQ0pdJn8tg03OSj2OzTK\nJ4hMsO/wt1xM8EDRobLZEosMadaYZUHzx8VU5RsCgYEAvFZbuXEcT0cocpLIUOaK\nOTf7VRLfvmSYaUAcZoEv0sDpExDiWPodWO6To8/vn5lL2tCsKiOKhkhAlIjRxkgF\nsSfj7I7HXKJS7/LBX6RXrem8qMTS2JTDs9pnBk5hb3DLjDg4pxNIdWiQjbeKvw8f\nvnr3m30yQqhKlte7Tt15exUCgYBzq7RbyR6Nfy2SFdYE7usJPjawohOaS/RwQyov\n2RK+nGlJH+GqnjD5VLbsCOm4mG3F2NtdFSSKo4XVCdwhUMMAGKQsIbTKOwN7qAw3\nmIx7Y2PUr76SakAPfDc0ZenJItnZBBE6WOE3Ht8Siaa5zFCRy2QlMZxdlTv1VRt7\neUuyiQKBgQDdXJO5+3h1HPxbYZcmNm/2CJUNw2ehU8vCiBXCcWPn7JukayHx+TXy\nyj0j/b1SvmKgjB+4JWluiqIU+QBjRjvb397QY1YoCEaGZd0zdFjTZwQksQ5AFst9\nCiD9OFXe/kkmIUQQra6aw1CoppyAfvAblp8uevLWb57xU3VUB3xeGg==\n-----END RSA PRIVATE KEY-----\n', - } - }, - (error, info) => { - // console.log(info); - if (error){ - console.log(error); - test.done(); - return; - } - test.deepEqual(info.accepted, [ 'discard@haraka.local' ]); - console.log(`Message sent: ${info.response}`); - test.done(); - }); - }, -} + it('gets a fs path', () => { + assert.ok(this.server.get_http_docroot()); + }) + }) + + describe('smtp_client', () => { + beforeEach((done) => { + _setupServer('localhost:2500', done) + }) -exports.requireAuthorized_SMTPS = { - setUp (done) { - _setupServer(this, '127.0.0.1:2465', done); - }, - tearDown: _tearDownServer, - 'rejects non-validated SMTPS connection': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2465, - secure: true, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false + afterEach(_tearDownServer) + + it('accepts SMTP message', () => { + + const server = { notes: { } }; + const cfg = { + connect_timeout: 2, } - }); - // give the SMTPS listener a second to start listening - setTimeout(() => { + const smtp_client = require('../smtp_client'); + + smtp_client.get_client(server, (client) => { + + client + .on('greeting', command => { + client.send_command('HELO', 'haraka.local'); + }) + .on('helo', () => { + client.send_command('MAIL', 'FROM:'); + }) + .on('mail', () => { + client.send_command('RCPT', 'TO:'); + }) + .on('rcpt', () => { + client.send_command('DATA'); + }) + .on('data', () => { + const message_stream = new message.stream( + { main : { spool_after : 1024 } }, "theMessageId" + ); + + message_stream.on('end', () => { + client.socket.write('.\r\n'); + }) + message_stream.add_line('Header: test\r\n'); + message_stream.add_line('\r\n'); + message_stream.add_line('I am body text\r\n'); + message_stream.add_line_end(); + + client.start_data(message_stream); + }) + .on('dot', () => { + assert.ok(1); + client.release(); + }) + .on('bad_code', (code, msg) => { + client.release(); + }); + + }, { port: 2500, host: 'localhost', cfg }); + }) + }) + + describe('nodemailer', () => { + beforeEach((done) => { + _setupServer('127.0.0.1:2503', done) + }) + + afterEach(_tearDownServer) + + it('accepts SMTP message', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2503, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false + } + }); transporter.sendMail({ from: '"Testalicious Matt" ', to: 'nobody-will-see-this@haraka.local', @@ -444,44 +262,71 @@ exports.requireAuthorized_SMTPS = { html: 'Hello world ?', }, (error, info) => { - if (error) { - // console.log(error); - if (error.message === 'socket hang up') { // node 6 & 8 - test.equal(error.message, 'socket hang up'); - } - else if (/alert certificate required/.test(error.message)) { // node 18 - test.ok(/alert certificate required/.test(error.message)) - } - else { // node 10+ - test.equal(error.message, 'Client network socket disconnected before secure TLS connection was established'); - } + if (error){ + console.log(error); + return; } - test.done(); - }); - }, 500); - }, -} + assert.deepEqual(info.accepted, [ 'discard@haraka.local' ]); + console.log(`Message sent: ${info.response}`); + done() + }) + }) + + it('accepts authenticated SMTP', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2503, + auth: { + user: 'matt', + pass: 'goodPass' + }, + requireTLS: true, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false + } + }) -exports.requireAuthorized_STARTTLS = { - setUp (done) { - _setupServer(this, '127.0.0.1:2587', done); - }, - 'rejects non-validated STARTTLS connection': test => { - - test.expect(1); - const nodemailer = require('nodemailer'); - const transporter = nodemailer.createTransport({ - host: '127.0.0.1', - port: 2587, - secure: false, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false - } - }); + transporter.sendMail({ + from: '"Testalicious Matt" ', + to: 'nobody-will-see-this@haraka.local', + envelope: { + from: 'Haraka Test ', + to: 'Discard Queue ', + }, + subject: 'Hello ✔', + text: 'Hello world ?', + html: 'Hello world ?', + }, + (error, info) => { + if (error){ + console.log(error); + return; + } + assert.deepEqual(info.accepted, [ 'discard@haraka.local' ]); + console.log(`Message sent: ${info.response}`); + done() + }) + }) + + it('rejects invalid auth', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2503, + auth: { + user: 'matt', + pass: 'badPass' + }, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false + } + }) - // give the SMTPS listener a second to start listening - setTimeout(() => { transporter.sendMail({ from: '"Testalicious Matt" ', to: 'nobody-will-see-this@haraka.local', @@ -494,17 +339,153 @@ exports.requireAuthorized_STARTTLS = { html: 'Hello world ?', }, (error, info) => { - if (error) { + if (error){ + assert.equal(error.code, 'EAUTH'); // console.log(error); - if (/alert certificate required/.test(error.message)) { // node 18 - test.ok(/alert certificate required/.test(error.message)) - } - else { - test.equal(error.message, 'Client network socket disconnected before secure TLS connection was established'); + return done(); + } + console.log(info.response); + done() + }) + }) + + it('DKIM validates signed message', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2503, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false + } + }) + + transporter.sendMail({ + from: '"Testalicious Matt" ', + to: 'nobody-will-see-this@haraka.local', + envelope: { + from: 'Haraka Test ', + to: 'Discard Queue ', + }, + subject: 'Hello ✔', + text: 'Hello world ?', + html: 'Hello world ?', + dkim: { + domainName: "test.simerson.com", + keySelector: "harakatest2017", + privateKey: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAxqoUAnQ9GB3iNnkS7coj0Iggd0nyryW062tpK95NC5UXmmAwIpUMfkYdiHY2o2duWYGF0Bp237M/QXKhJYTXfsgkwP/bq9OGWtRZxHPHhbhdjbiI\nqObi6zvYcxrI77gpWDDvruhMeS9Hwa1R99pLUWd4PsuYTzbV/jwu2pz+XZXXXNEU\nVxzDAAj0yF7mwxHMLzQfR+hdhWcrgN0stUP0o7hm7hoOP8IWgcSW3JiQYavIKoI4\nm4+I9I1LzDJN2rHVnQvmjUrqqpG7X6SyFVFtuTWGaMqf1Cj/t8eSvU9VdgLFllS8\ntThqUZHq5S5hm8M8VzLuQLG9U0dtFolcFmJkbQIDAQABAoIBAB4fUbNhjpXmihM6\nXm1htfZ7fXi45Kw76me7vJGjPklgTNjidsn3kZJf7UBwtC4ok6nMos6ABMA8fH3e\n9KIst0QI8tG0ucke5INHKWlJKNqUrtK7RTVe9M84HsStLgRzBwnRObZqkJXbXmT2\nc7RCDCOGrcvPsQNpzB6lX3FUVpk3x24RXpQV1qSgH8yuHSPc1C6rssXwPAgnESfS\nK3MHRx2CLZvTTkq/YCsT+wS/O9RWPCVOYuWaa5DDDAIp3Yw1wYq9Upoh0BdIFC3U\nWm+5Cr3o9wxcvS6+W2RA6I51eymzvCU5ZakWt/bnUDb6/ByxsWOn5rL4WfPpCwE4\nnuC72v0CgYEA9imEq6a0GoaEsMoR7cxT7uXKimQH+Jaq3CGkuh0iN32F4FXhuUKz\nLYKSLCZzpb1MiDJv6BBchV6uSQ6ATo1cZ8WzYQISikk175bf0SPom591OZElvKA2\nSOrTrXtbl33YbWZEgyEcpTgelVi5ys9rj4eKkMvM0lwRmW6gctEFXRcCgYEAzpqc\nR/wqPjgPhpF1CZtdEwOZg4kkOig8CBcuQ7o/hDG7N69A9ZbeJO8eD+gKDrHRfkYr\nTH/UdkZGjilBk/lxnpIZpyBLxQ6UdhNPuwtxXKAvuSN+aQ0pdJn8tg03OSj2OzTK\nJ4hMsO/wt1xM8EDRobLZEosMadaYZUHzx8VU5RsCgYEAvFZbuXEcT0cocpLIUOaK\nOTf7VRLfvmSYaUAcZoEv0sDpExDiWPodWO6To8/vn5lL2tCsKiOKhkhAlIjRxkgF\nsSfj7I7HXKJS7/LBX6RXrem8qMTS2JTDs9pnBk5hb3DLjDg4pxNIdWiQjbeKvw8f\nvnr3m30yQqhKlte7Tt15exUCgYBzq7RbyR6Nfy2SFdYE7usJPjawohOaS/RwQyov\n2RK+nGlJH+GqnjD5VLbsCOm4mG3F2NtdFSSKo4XVCdwhUMMAGKQsIbTKOwN7qAw3\nmIx7Y2PUr76SakAPfDc0ZenJItnZBBE6WOE3Ht8Siaa5zFCRy2QlMZxdlTv1VRt7\neUuyiQKBgQDdXJO5+3h1HPxbYZcmNm/2CJUNw2ehU8vCiBXCcWPn7JukayHx+TXy\nyj0j/b1SvmKgjB+4JWluiqIU+QBjRjvb397QY1YoCEaGZd0zdFjTZwQksQ5AFst9\nCiD9OFXe/kkmIUQQra6aw1CoppyAfvAblp8uevLWb57xU3VUB3xeGg==\n-----END RSA PRIVATE KEY-----\n', + } + }, + (error, info) => { + // console.log(info); + if (error){ + console.log(error); + return; + } + assert.deepEqual(info.accepted, [ 'discard@haraka.local' ]); + console.log(`Message sent: ${info.response}`); + done() + }) + }) + }) + + describe('requireAuthorized_SMTPS', () => { + beforeEach((done) => { + _setupServer('127.0.0.1:2465', done) + }) + + afterEach(_tearDownServer) + + it('rejects non-validated SMTPS connection', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2465, + secure: true, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false + } + }) + + // give the SMTPS listener a second to start listening + setTimeout(() => { + transporter.sendMail({ + from: '"Testalicious Matt" ', + to: 'nobody-will-see-this@haraka.local', + envelope: { + from: 'Haraka Test ', + to: 'Discard Queue ', + }, + subject: 'Hello ✔', + text: 'Hello world ?', + html: 'Hello world ?', + }, + (error, info) => { + if (error) { + // console.log(error); + if (error.message === 'socket hang up') { // node 6 & 8 + assert.equal(error.message, 'socket hang up'); + } + else if (/alert certificate required/.test(error.message)) { // node 18 + assert.ok(/alert certificate required/.test(error.message)) + } + else { // node 10+ + assert.equal(error.message, 'Client network socket disconnected before secure TLS connection was established'); + } } + done() + }) + }, 500); + }) + }) + + describe('requireAuthorized_STARTTLS', () => { + beforeEach((done) => { + _setupServer('127.0.0.1:2587', done) + }) + + it('rejects non-validated STARTTLS connection', (done) => { + + const nodemailer = require('nodemailer'); + const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 2587, + secure: false, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false } - test.done(); }); - }, 500); - }, -} + + // give the SMTPS listener a half second to start listening + setTimeout(() => { + transporter.sendMail({ + from: '"Testalicious Matt" ', + to: 'nobody-will-see-this@haraka.local', + envelope: { + from: 'Haraka Test ', + to: 'Discard Queue ', + }, + subject: 'Hello ✔', + text: 'Hello world ?', + html: 'Hello world ?', + }, + (error, info) => { + if (error) { + // console.log(error); + if (/alert certificate required/.test(error.message)) { // node 18 + assert.ok(/alert certificate required/.test(error.message)) + } + else { + assert.equal(error.message, 'Client network socket disconnected before secure TLS connection was established'); + } + } + done() + }) + }, 500); + }) + }) +}) \ No newline at end of file diff --git a/tests/smtp_client.js b/tests/smtp_client.js index 106d59435..01a0c4306 100644 --- a/tests/smtp_client.js +++ b/tests/smtp_client.js @@ -1,80 +1,299 @@ +const assert = require('node:assert') +const path = require('node:path'); -const path = require('path'); -const vm_harness = require('./fixtures/vm_harness'); - -vm_harness.add_tests( - path.join(__dirname, '..', 'smtp_client.js'), - path.join(__dirname, 'smtp_client') + path.sep, - exports -); +const fixtures = require('haraka-test-fixtures'); +const message = require('haraka-email-message') const smtp_client = require('../smtp_client'); -const fixtures = require('haraka-test-fixtures'); +const test_socket = require('./fixtures/line_socket') function getClientOpts (socket) { return { port: 25, host: 'localhost', connect_timeout: 30, idle_timeout: 30, socket } } -exports.testUpgradeIsCalledOnSTARTTLS = test => { - test.expect(1); +describe('smtp_client', () => { - const plugin = new fixtures.plugin('queue/smtp_forward'); + it('testUpgradeIsCalledOnSTARTTLS', () => { - // switch config directory to 'tests/config' - plugin.config = plugin.config.module_config(path.resolve('tests')); + const plugin = new fixtures.plugin('queue/smtp_forward'); - plugin.register(); + // switch config directory to 'tests/config' + plugin.config = plugin.config.module_config(path.resolve('tests')); - const cmds = {}; - let upgradeArgs = {}; + plugin.register(); - const socket = { - setTimeout: arg => { }, - setKeepAlive: arg => { }, - on: (eventName, callback) => { - cmds[eventName] = callback; - }, - upgrade: arg => { - upgradeArgs = arg; - } - }; + const cmds = {}; + let upgradeArgs = {}; - const client = new smtp_client.smtp_client(getClientOpts(socket)); - client.load_tls_config({ key: Buffer.from('OutboundTlsKeyLoaded')}); + const socket = { + setTimeout: arg => { }, + setKeepAlive: arg => { }, + on: (eventName, callback) => { + cmds[eventName] = callback; + }, + upgrade: arg => { + upgradeArgs = arg; + } + }; - client.command = 'starttls'; - cmds.line('250 Hello client.example.com\r\n'); + const client = new smtp_client.smtp_client(getClientOpts(socket)); + client.load_tls_config({ key: Buffer.from('OutboundTlsKeyLoaded')}); - const { StringDecoder } = require('string_decoder'); - const decoder = new StringDecoder('utf8'); + client.command = 'starttls'; + cmds.line('250 Hello client.example.com\r\n'); - const cent = Buffer.from(upgradeArgs.key); - test.equal(decoder.write(cent), 'OutboundTlsKeyLoaded'); + const { StringDecoder } = require('string_decoder'); + const decoder = new StringDecoder('utf8'); - test.done(); -} + const cent = Buffer.from(upgradeArgs.key); + assert.equal(decoder.write(cent), 'OutboundTlsKeyLoaded'); + }) -exports.startTLS = test => { - test.expect(1); + it('startTLS', () => { - let cmd = ''; + let cmd = ''; - const socket = { - setTimeout: arg => { }, - setKeepAlive: arg => { }, - on: (eventName, callback) => { }, - upgrade: arg => { }, - write: arg => { cmd = arg; } - }; + const socket = { + setTimeout: arg => { }, + setKeepAlive: arg => { }, + on: (eventName, callback) => { }, + upgrade: arg => { }, + write: arg => { cmd = arg; } + }; - const client = new smtp_client.smtp_client(getClientOpts(socket)); - client.tls_options = {}; + const client = new smtp_client.smtp_client(getClientOpts(socket)); + client.tls_options = {}; - client.secured = false; - client.response = [ 'STARTTLS' ] + client.secured = false; + client.response = [ 'STARTTLS' ] - smtp_client.onCapabilitiesOutbound(client, false, undefined, { 'enable_tls': true }); + smtp_client.onCapabilitiesOutbound(client, false, undefined, { 'enable_tls': true }); - test.equal(cmd, 'STARTTLS\r\n'); - test.done(); -} + assert.equal(cmd, 'STARTTLS\r\n'); + }) + + describe('auth', () => { + + beforeEach((done) => { + smtp_client.get_client({ notes: {}}, (client) => { + this.client = client + done() + }, + { + socket: test_socket.connect(), + } + ) + }) + + it('authenticates during SMTP conversation', (done) => { + + const message_stream = new message.stream( + { main : { spool_after : 1024 } }, "123456789" + ); + + const data = []; + let reading_body = false; + data.push('220 hi'); + + this.client.on('greeting', command => { + assert.equal(this.client.response[0], 'hi'); + assert.equal('EHLO', command); + this.client.send_command(command, 'example.com'); + }); + + data.push('EHLO example.com'); + data.push('250 hello'); + + this.client.on('helo', () => { + assert.equal(this.client.response[0], 'hello'); + this.client.send_command('AUTH', 'PLAIN AHRlc3QAdGVzdHBhc3M='); + this.client.send_command('MAIL', 'FROM: me@example.com'); + }); + + data.push('AUTH PLAIN AHRlc3QAdGVzdHBhc3M='); // test/testpass + data.push('235 Authentication successful.'); + + data.push('MAIL FROM: me@example.com'); + data.push('250 sender ok'); + + this.client.on('mail', () => { + assert.equal(this.client.response[0], 'sender ok'); + this.client.send_command('RCPT', 'TO: you@example.com'); + }); + + data.push('RCPT TO: you@example.com'); + data.push('250 recipient ok'); + + this.client.on('rcpt', () => { + assert.equal(this.client.response[0], 'recipient ok'); + this.client.send_command('DATA'); + }); + + data.push('DATA'); + data.push('354 go ahead'); + + this.client.on('data', () => { + assert.equal(this.client.response[0], 'go ahead'); + this.client.start_data(message_stream); + message_stream.on('end', () => { + this.client.socket.write('.\r\n'); + }); + message_stream.add_line('Header: test\r\n'); + message_stream.add_line('\r\n'); + message_stream.add_line('hi\r\n'); + message_stream.add_line_end(); + }); + + data.push('.'); + data.push('250 message queued'); + + this.client.on('dot', () => { + assert.equal(this.client.response[0], 'message queued'); + this.client.send_command('QUIT'); + }); + + data.push('QUIT'); + data.push('221 goodbye'); + + this.client.on('quit', () => { + assert.equal(this.client.response[0], 'goodbye'); + done() + }); + + this.client.socket.write = function (line) { + if (data.length == 0) { + assert.ok(false); + return; + } + assert.equal(`${data.shift()}\r\n`, line); + if (reading_body && line == '.\r\n') { + reading_body = false; + } + if (!reading_body) { + if (line == 'DATA\r\n') { + reading_body = true; + } + while (true) { + const line2 = data.shift(); + this.emit('line', `${line2}\r\n`); + if (line2[3] == ' ') break; + } + } + + return true; + }; + + this.client.socket.emit('line', data.shift()); + }) + }) + + describe('basic', () => { + + beforeEach((done) => { + smtp_client.get_client({notes: {}}, (client) => { + this.client = client + done() + }, + { + socket: test_socket.connect(), + }) + }) + + it('conducts a SMTP session', (done) => { + + const message_stream = new message.stream( + { main : { spool_after : 1024 } }, '123456789' + ); + + const data = []; + let reading_body = false; + data.push('220 hi'); + + this.client.on('greeting', command => { + assert.equal(this.client.response[0], 'hi'); + assert.equal('EHLO', command); + this.client.send_command(command, 'example.com'); + }); + + data.push('EHLO example.com'); + data.push('250 hello'); + + this.client.on('helo', () => { + assert.equal(this.client.response[0], 'hello'); + this.client.send_command('MAIL', 'FROM: me@example.com'); + }); + + data.push('MAIL FROM: me@example.com'); + data.push('250 sender ok'); + + this.client.on('mail', () => { + assert.equal(this.client.response[0], 'sender ok'); + this.client.send_command('RCPT', 'TO: you@example.com'); + }); + + data.push('RCPT TO: you@example.com'); + data.push('250 recipient ok'); + + this.client.on('rcpt', () => { + assert.equal(this.client.response[0], 'recipient ok'); + this.client.send_command('DATA'); + }); + + data.push('DATA'); + data.push('354 go ahead'); + + this.client.on('data', () => { + assert.equal(this.client.response[0], 'go ahead'); + this.client.start_data(message_stream); + message_stream.on('end', () => { + this.client.socket.write('.\r\n'); + }); + message_stream.add_line('Header: test\r\n'); + message_stream.add_line('\r\n'); + message_stream.add_line('hi\r\n'); + message_stream.add_line_end(); + }); + + data.push('.'); + data.push('250 message queued'); + + this.client.on('dot', () => { + assert.equal(this.client.response[0], 'message queued'); + this.client.send_command('QUIT'); + }); + + data.push('QUIT'); + data.push('221 goodbye'); + + this.client.on('quit', () => { + assert.equal(this.client.response[0], 'goodbye'); + done() + }); + + this.client.socket.write = function (line) { + if (data.length == 0) { + assert.ok(false); + return; + } + assert.equal(`${data.shift() }\r\n`, line); + if (reading_body && line == '.\r\n') { + reading_body = false; + } + if (reading_body) return true; + + if (line == 'DATA\r\n') { + reading_body = true; + } + while (true) { + const line2 = data.shift(); + this.emit('line', `${line2 }\r\n`); + if (line2[3] == ' ') break; + } + + return true; + }; + + this.client.socket.emit('line', data.shift()); + }) + }) +}) diff --git a/tests/smtp_client/auth.js b/tests/smtp_client/auth.js deleted file mode 100644 index 9e8a99f6f..000000000 --- a/tests/smtp_client/auth.js +++ /dev/null @@ -1,105 +0,0 @@ -const message = require('haraka-email-message') - -test.expect(15); -const server = {notes: {}}; - -exports.get_client(server, (smtp_client) => { - - const message_stream = new message.stream( - { main : { spool_after : 1024 } }, "123456789" - ); - - const data = []; - let reading_body = false; - data.push('220 hi'); - - smtp_client.on('greeting', command => { - test.equals(smtp_client.response[0], 'hi'); - test.equals('EHLO', command); - smtp_client.send_command(command, 'example.com'); - }); - - data.push('EHLO example.com'); - data.push('250 hello'); - - smtp_client.on('helo', () => { - test.equals(smtp_client.response[0], 'hello'); - smtp_client.send_command('AUTH', 'PLAIN AHRlc3QAdGVzdHBhc3M='); - smtp_client.send_command('MAIL', 'FROM: me@example.com'); - }); - - data.push('AUTH PLAIN AHRlc3QAdGVzdHBhc3M='); // test/testpass - data.push('235 Authentication successful.'); - - data.push('MAIL FROM: me@example.com'); - data.push('250 sender ok'); - - smtp_client.on('mail', () => { - test.equals(smtp_client.response[0], 'sender ok'); - smtp_client.send_command('RCPT', 'TO: you@example.com'); - }); - - data.push('RCPT TO: you@example.com'); - data.push('250 recipient ok'); - - smtp_client.on('rcpt', () => { - test.equals(smtp_client.response[0], 'recipient ok'); - smtp_client.send_command('DATA'); - }); - - data.push('DATA'); - data.push('354 go ahead'); - - smtp_client.on('data', () => { - test.equals(smtp_client.response[0], 'go ahead'); - smtp_client.start_data(message_stream); - message_stream.on('end', () => { - smtp_client.socket.write('.\r\n'); - }); - message_stream.add_line('Header: test\r\n'); - message_stream.add_line('\r\n'); - message_stream.add_line('hi\r\n'); - message_stream.add_line_end(); - }); - - data.push('.'); - data.push('250 message queued'); - - smtp_client.on('dot', () => { - test.equals(smtp_client.response[0], 'message queued'); - smtp_client.send_command('QUIT'); - }); - - data.push('QUIT'); - data.push('221 goodbye'); - - smtp_client.on('quit', () => { - test.equals(smtp_client.response[0], 'goodbye'); - test.done(); - }); - - smtp_client.socket.write = function (line) { - if (data.length == 0) { - test.ok(false); - return; - } - test.equals(`${data.shift()}\r\n`, line); - if (reading_body && line == '.\r\n') { - reading_body = false; - } - if (!reading_body) { - if (line == 'DATA\r\n') { - reading_body = true; - } - while (true) { - const line2 = data.shift(); - this.emit('line', `${line2}\r\n`); - if (line2[3] == ' ') break; - } - } - - return true; - }; - - smtp_client.socket.emit('line', data.shift()); -}); diff --git a/tests/smtp_client/basic.js b/tests/smtp_client/basic.js deleted file mode 100644 index 39a0ede05..000000000 --- a/tests/smtp_client/basic.js +++ /dev/null @@ -1,101 +0,0 @@ -const message = require('haraka-email-message') - -test.expect(14); -const server = {notes: {}}; - -exports.get_client(server, (smtp_client) => { - - const message_stream = new message.stream( - { main : { spool_after : 1024 } }, '123456789' - ); - - const data = []; - let reading_body = false; - data.push('220 hi'); - - smtp_client.on('greeting', command => { - test.equals(smtp_client.response[0], 'hi'); - test.equals('EHLO', command); - smtp_client.send_command(command, 'example.com'); - }); - - data.push('EHLO example.com'); - data.push('250 hello'); - - smtp_client.on('helo', () => { - test.equals(smtp_client.response[0], 'hello'); - smtp_client.send_command('MAIL', 'FROM: me@example.com'); - }); - - data.push('MAIL FROM: me@example.com'); - data.push('250 sender ok'); - - smtp_client.on('mail', () => { - test.equals(smtp_client.response[0], 'sender ok'); - smtp_client.send_command('RCPT', 'TO: you@example.com'); - }); - - data.push('RCPT TO: you@example.com'); - data.push('250 recipient ok'); - - smtp_client.on('rcpt', () => { - test.equals(smtp_client.response[0], 'recipient ok'); - smtp_client.send_command('DATA'); - }); - - data.push('DATA'); - data.push('354 go ahead'); - - smtp_client.on('data', () => { - test.equals(smtp_client.response[0], 'go ahead'); - smtp_client.start_data(message_stream); - message_stream.on('end', () => { - smtp_client.socket.write('.\r\n'); - }); - message_stream.add_line('Header: test\r\n'); - message_stream.add_line('\r\n'); - message_stream.add_line('hi\r\n'); - message_stream.add_line_end(); - }); - - data.push('.'); - data.push('250 message queued'); - - smtp_client.on('dot', () => { - test.equals(smtp_client.response[0], 'message queued'); - smtp_client.send_command('QUIT'); - }); - - data.push('QUIT'); - data.push('221 goodbye'); - - smtp_client.on('quit', () => { - test.equals(smtp_client.response[0], 'goodbye'); - test.done(); - }); - - smtp_client.socket.write = function (line) { - if (data.length == 0) { - test.ok(false); - return; - } - test.equals(`${data.shift() }\r\n`, line); - if (reading_body && line == '.\r\n') { - reading_body = false; - } - if (reading_body) return true; - - if (line == 'DATA\r\n') { - reading_body = true; - } - while (true) { - const line2 = data.shift(); - this.emit('line', `${line2 }\r\n`); - if (line2[3] == ' ') break; - } - - return true; - }; - - smtp_client.socket.emit('line', data.shift()); -}); diff --git a/tests/tls_socket.js b/tests/tls_socket.js index ad7b38d2f..1625855e3 100644 --- a/tests/tls_socket.js +++ b/tests/tls_socket.js @@ -1,225 +1,208 @@ +const assert = require('node:assert') + const fs = require('fs') const path = require('path') const os = require('os') -function _setup (done) { +const _setup = (done) => { this.socket = require('../tls_socket'); // use tests/config instead of ./config this.socket.config = this.socket.config.module_config(path.resolve('tests')); - done(); } -exports.tls_socket = { - setUp: _setup, - 'loads' (test) { - test.expect(1); - test.ok(this.socket); - test.done(); - }, - 'exports createConnection' (test) { - test.expect(1); - test.equal(typeof this.socket.createConnection, 'function'); - test.done(); - }, - 'exports createServer' (test) { - test.expect(1); +describe('tls_socket', () => { + beforeEach(_setup) + + it('loads', () => { + assert.ok(this.socket); + }) + it('exports createConnection', () => { + assert.equal(typeof this.socket.createConnection, 'function'); + }) + it('exports createServer', () => { // console.log(this.socket); - test.equal(typeof this.socket.createServer, 'function'); - test.done(); - }, - 'exports shutdown' (test) { - test.expect(1); + assert.equal(typeof this.socket.createServer, 'function'); + }) + it('exports shutdown', () => { // console.log(this.socket); - test.equal(typeof this.socket.shutdown, 'function'); - test.done(); - }, -} + assert.equal(typeof this.socket.shutdown, 'function'); + }) -exports.createServer = { - setUp: _setup, - 'returns a net.Server' (test) { - test.expect(1); - const server = this.socket.createServer(sock => { - console.log(sock); - }); - test.ok(server); - test.done(); - } -} + describe('createServer', () => { + beforeEach(_setup) -exports.saveOpt = { - setUp: _setup, - 'saveOpt' (test) { - test.expect(1); - this.socket.saveOpt('*', 'dhparam', 'a file name'); - test.ok(this.socket.certsByHost['*'].dhparam); - // console.log(this.socket.certsByHost['*']); - test.done(); - } -} + it('returns a net.Server', () => { + const server = this.socket.createServer(sock => { + console.log(sock); + }); + assert.ok(server); + }) + }) -exports.load_tls_ini = { - setUp: _setup, - 'tls.ini loads' (test) { - test.expect(2); - test.ok(this.socket.load_tls_ini().main !== undefined); - test.ok(this.socket.certsByHost['*'].key); - // console.log(this.socket.cfg); - // console.log(this.socket.certsByHost); - test.done(); - }, -} + describe('saveOpt', () => { + beforeEach(_setup) -exports.get_loud_certs_dir = { - setUp: _setup, - 'loads certs from tests/loud/config/tls' (test) { - test.expect(2); - this.socket.config = this.socket.config.module_config(path.resolve('tests', 'loud')); - this.socket.get_certs_dir('tls', (err, certs) => { - test.ifError(err); - test.ok(certs); - test.done(); + it('saveOpt', () => { + this.socket.saveOpt('*', 'dhparam', 'a file name'); + assert.ok(this.socket.certsByHost['*'].dhparam); + // console.log(this.socket.certsByHost['*']); }) - } -} + }) + + describe('load_tls_ini', () => { + beforeEach(_setup) -exports.get_certs_dir = { - setUp: _setup, - 'loads certs from tests/config/tls' (test) { - test.expect(2); - this.socket.config = this.socket.config.module_config(path.resolve('tests')); - this.socket.get_certs_dir('tls', (err, certs) => { - test.ifError(err); - test.ok(certs); - test.done(); + it('tls.ini loads', () => { + assert.ok(this.socket.load_tls_ini().main !== undefined); + assert.ok(this.socket.certsByHost['*'].key); + // console.log(this.socket.cfg); + // console.log(this.socket.certsByHost); }) - } -} + }) + + describe('get_loud_certs_dir', () => { + beforeEach(_setup) -exports.getSocketOpts = { - setUp: _setup, - 'gets socket opts for *' (test) { - test.expect(2); - this.socket.get_certs_dir('tls', () => { - this.socket.getSocketOpts('*', (opts) => { - // console.log(opts); - test.ok(opts.key); - test.ok(opts.cert); - test.done(); + it('loads certs from tests/loud/config/tls', () => { + this.socket.config = this.socket.config.module_config(path.resolve('tests', 'loud')); + this.socket.get_certs_dir('tls', (err, certs) => { + assert.ifError(err); + assert.ok(certs); }) }) - }, -} + }) -exports.ensureDhparams = { - setUp : _setup, - 'generates a missing dhparams file' (test) { - test.expect(2); - this.socket.load_tls_ini(); - this.socket.ensureDhparams((err, dhparams) => { - // console.log(dhparams); - test.ifError(err); - test.ok(dhparams); - test.done(); + describe('get_certs_dir', () => { + beforeEach(_setup) + + it('loads certs from tests/config/tls', () => { + this.socket.config = this.socket.config.module_config(path.resolve('tests')); + this.socket.get_certs_dir('tls', (err, certs) => { + assert.ifError(err); + assert.ok(certs); + }) }) - }, -} + }) + + describe('getSocketOpts', () => { + beforeEach(_setup) + + it('gets socket opts for *', () => { + this.socket.get_certs_dir('tls', () => { + this.socket.getSocketOpts('*', (opts) => { + // console.log(opts); + assert.ok(opts.key); + assert.ok(opts.cert); + }) + }) + }) + }) -exports.load_tls_ini2 = { - setUp (done) { - this.socket = require('../tls_socket'); - delete process.env.HARAKA_TEST_DIR; - done(); - }, - 'loads missing tls.ini default config' (test) { - test.expect(1); - this.socket.config = this.socket.config.module_config(path.resolve('non-exist')); - test.deepEqual(this.socket.load_tls_ini(), - { + describe('ensureDhparams', () => { + beforeEach(_setup) + it('generates a missing dhparams file', () => { + this.socket.load_tls_ini(); + this.socket.ensureDhparams((err, dhparams) => { + // console.log(dhparams); + assert.ifError(err); + assert.ok(dhparams); + }) + }) + }) + + describe('load_tls_ini2', () => { + beforeEach((done) => { + this.socket = require('../tls_socket'); + delete process.env.HARAKA_TEST_DIR; + done(); + }) + + it('loads missing tls.ini default config', () => { + this.socket.config = this.socket.config.module_config(path.resolve('non-exist')); + assert.deepEqual(this.socket.load_tls_ini(), + { + main: { + requestCert: true, + rejectUnauthorized: false, + honorCipherOrder: true, + requestOCSP: false, + // enableOCSPStapling: false, + requireAuthorized: [], + mutual_tls: false, + no_starttls_ports: [], + }, + redis: { disable_for_failed_hosts: false }, + no_tls_hosts: {}, + mutual_auth_hosts: {}, + mutual_auth_hosts_exclude: {}, + }); + }) + + it('loads tls.ini from test dir', () => { + this.socket.config = this.socket.config.module_config(path.resolve('tests')); + assert.deepEqual(this.socket.load_tls_ini(), { main: { requestCert: true, rejectUnauthorized: false, honorCipherOrder: true, requestOCSP: false, - // enableOCSPStapling: false, - requireAuthorized: [], + key: 'tls_key.pem', + cert: 'tls_cert.pem', + dhparam: 'dhparams.pem', + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384', + minVersion: 'TLSv1', + requireAuthorized: [2465, 2587], mutual_tls: false, - no_starttls_ports: [], + no_starttls_ports: [2525], }, redis: { disable_for_failed_hosts: false }, no_tls_hosts: {}, mutual_auth_hosts: {}, mutual_auth_hosts_exclude: {}, - }); - test.done(); - }, - 'loads tls.ini from test dir' (test) { - test.expect(1); - this.socket.config = this.socket.config.module_config(path.resolve('tests')); - test.deepEqual(this.socket.load_tls_ini(), { - main: { - requestCert: true, - rejectUnauthorized: false, - honorCipherOrder: true, - requestOCSP: false, - key: 'tls_key.pem', - cert: 'tls_cert.pem', - dhparam: 'dhparams.pem', - ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384', - minVersion: 'TLSv1', - requireAuthorized: [2465, 2587], - mutual_tls: false, - no_starttls_ports: [2525], - }, - redis: { disable_for_failed_hosts: false }, - no_tls_hosts: {}, - mutual_auth_hosts: {}, - mutual_auth_hosts_exclude: {}, - outbound: { - key: 'outbound_tls_key.pem', - cert: 'outbound_tls_cert.pem', - ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', - minVersion: 'TLSv1', - dhparam: 'dhparams.pem', - rejectUnauthorized: false, - requestCert: false, - honorCipherOrder: false, - force_tls_hosts: ['first.example.com', 'second.example.net'], - } - }); - test.done(); - }, -} + outbound: { + key: 'outbound_tls_key.pem', + cert: 'outbound_tls_cert.pem', + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + minVersion: 'TLSv1', + dhparam: 'dhparams.pem', + rejectUnauthorized: false, + requestCert: false, + honorCipherOrder: false, + force_tls_hosts: ['first.example.com', 'second.example.net'], + } + }) + }) + }) + + describe('parse_x509', () => { + beforeEach(_setup) -exports.parse_x509 = { - setUp: _setup, - 'returns empty object on empty input' (test) { - const res = this.socket.parse_x509(); - test.deepEqual(res, {}); - test.done(); - }, - 'returns key from BEGIN PRIVATE KEY block' (test) { - const res = this.socket.parse_x509('-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--\n-its me-\n'); - test.deepEqual( - res.key.toString(), - '-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--' - ); - test.deepEqual(res.cert, undefined); - test.done(); - }, - 'returns key from BEGIN RSA PRIVATE KEY block' (test) { - const res = this.socket.parse_x509('-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--\n-its me-\n'); - test.deepEqual( - res.key.toString(), - '-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--' - ); - test.deepEqual(res.cert, undefined); - test.done(); - }, - 'returns a key and certificate chain' (test) { - const str = `-----BEGIN RSA PRIVATE KEY----- + it('returns empty object on empty input', () => { + const res = this.socket.parse_x509(); + assert.deepEqual(res, {}); + }) + + it('returns key from BEGIN PRIVATE KEY block', () => { + const res = this.socket.parse_x509('-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--\n-its me-\n'); + assert.deepEqual( + res.key.toString(), + '-BEGIN PRIVATE KEY-\nhello\n--END PRIVATE KEY--', + ); + assert.deepEqual(res.cert, undefined); + }) + it('returns key from BEGIN RSA PRIVATE KEY block', () => { + const res = this.socket.parse_x509('-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--\n-its me-\n'); + assert.deepEqual( + res.key.toString(), + '-BEGIN RSA PRIVATE KEY-\nhello\n--END RSA PRIVATE KEY--', + ); + assert.deepEqual(res.cert, undefined); + }) + + it('returns a key and certificate chain', () => { + const str = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAoDGOlvw6lQptaNwqxYsW4aJCPIgvjYw3qA9Y0qykp8I8PapT ercA8BsInrZg5+3wt2PT1+REprBvv6xfHyQ08o/udsSCBRf4Awadp0fxzUulENNi 3wWuuPy0WgaE4jam7tWItDBeEhXkEfcMTr9XkFxenuTcNw9O1+E8TtNP9KMmJDAe @@ -256,25 +239,25 @@ WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 -----END CERTIFICATE-----` - const res = this.socket.parse_x509(str); - test.deepEqual(res.key.length, 446); - test.deepEqual(res.cert.length, 1195); - test.done(); - }, - 'returns cert and key from EC pem' (test) { - const fp = fs.readFileSync(path.join('tests','config','tls','ec.pem')) - const res = this.socket.parse_x509(fp.toString()) - test.deepEqual( - res.key.toString().split(os.EOL).join('\n'), - `-----BEGIN EC PRIVATE KEY----- + const res = this.socket.parse_x509(str); + assert.deepEqual(res.key.length, 446); + assert.deepEqual(res.cert.length, 1195); + }) + + it('returns cert and key from EC pem', () => { + const fp = fs.readFileSync(path.join('tests','config','tls','ec.pem')) + const res = this.socket.parse_x509(fp.toString()) + assert.deepEqual( + res.key.toString().split(os.EOL).join('\n'), + `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIIDhiI5q6l7txfMJ6kIEYjK12EFcHLvDIkfWIwzdZBsloAoGCCqGSM49 AwEHoUQDQgAEZg2nHEFy9nquFPF3DQyQE28e/ytjXeb4nD/8U+L4KHKFtglaX3R4 uZ+5JcwfcDghpL4Z8h4ouUD/xqe957e2+g== -----END EC PRIVATE KEY-----` - ); - test.deepEqual( - res.cert.toString().split(os.EOL).join('\n'), - `-----BEGIN CERTIFICATE----- + ); + assert.deepEqual( + res.cert.toString().split(os.EOL).join('\n'), + `-----BEGIN CERTIFICATE----- MIICaTCCAg+gAwIBAgIUEDa9VX16wCdo97WvIk7jyEBz1wQwCgYIKoZIzj0EAwIw gYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdT ZWF0dGxlMRQwEgYDVQQKDAtIYXJha2EgTWFpbDEXMBUGA1UEAwwObWFpbC5oYXJh @@ -289,45 +272,36 @@ Rz0mR/YwHwYDVR0jBBgwFoAU094ROMLHmLEspT4ZoCfXRz0mR/YwDwYDVR0TAQH/ BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAsmshzvMDjmYDHyGRrKdMmsnnESFd GMtfRXYIv0AZe7ICIGD2Sta9LL0zZ44ARGXhh+sPjxd78I/+0FdIPsofr2I+ -----END CERTIFICATE-----`); - test.done(); - }, -} + }) + }) -exports.parse_x509_names = { - setUp: _setup, - 'extracts nictool.com from x509 Subject CN' (test) { - test.expect(1); - const r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=nictool.com\n Subject Public Key Info:\n'); - test.deepEqual(r, ['nictool.com']); - test.done(); - }, - 'extracts haraka.local from x509 Subject CN' (test) { - test.expect(1); - const r = this.socket.parse_x509_names(' Validity\n Not Before: Mar 4 23:28:49 2017 GMT\n Not After : Mar 3 23:28:49 2023 GMT\n Subject: C=US, ST=Washington, L=Seattle, O=Haraka, CN=haraka.local/emailAddress=matt@haraka.local\n Subject Public Key Info:\n Public Key Algorithm: rsaEncryption\n'); - test.deepEqual(r, ['haraka.local']); - test.done(); - }, - 'extracts host names from X509v3 Subject Alternative Name' (test) { - test.expect(1); - const r = this.socket.parse_x509_names(' CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); - test.deepEqual(r, ['nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); - test.done(); - }, - 'extracts host names from both' (test) { - test.expect(2); - - let r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=nictool.com\n Subject Public Key Info:\n CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); - test.deepEqual(r, ['nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); - - r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=foo.nictool.com\n Subject Public Key Info:\n CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); - test.deepEqual(r, ['foo.nictool.com', 'nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); - - test.done(); - }, - 'extracts expiration date' (test) { - test.expect(1); - const r = this.socket.parse_x509_expire('foo', 'Validity\n Not Before: Mar 4 23:28:49 2017 GMT\n Not After : Mar 3 23:28:49 2023 GMT\n Subject'); - test.deepEqual(r, new Date('2023-03-03T23:28:49.000Z')); - test.done(); - }, -} + describe('parse_x509_names', () => { + beforeEach(_setup) + + it('extracts nictool.com from x509 Subject CN', () => { + const r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=nictool.com\n Subject Public Key Info:\n'); + assert.deepEqual(r, ['nictool.com']); + }) + it('extracts haraka.local from x509 Subject CN', () => { + const r = this.socket.parse_x509_names(' Validity\n Not Before: Mar 4 23:28:49 2017 GMT\n Not After : Mar 3 23:28:49 2023 GMT\n Subject: C=US, ST=Washington, L=Seattle, O=Haraka, CN=haraka.local/emailAddress=matt@haraka.local\n Subject Public Key Info:\n Public Key Algorithm: rsaEncryption\n'); + assert.deepEqual(r, ['haraka.local']); + }) + it('extracts host names from X509v3 Subject Alternative Name', () => { + const r = this.socket.parse_x509_names(' CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); + assert.deepEqual(r, ['nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); + }) + it('extracts host names from both', () => { + + let r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=nictool.com\n Subject Public Key Info:\n CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); + assert.deepEqual(r, ['nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); + + r = this.socket.parse_x509_names(' Validity\n Not Before: Jan 15 22:47:00 2017 GMT\n Not After : Apr 15 22:47:00 2017 GMT\n Subject: CN=foo.nictool.com\n Subject Public Key Info:\n CA Issuers - URI:http://cert.int-x3.letsencrypt.org/\n\n X509v3 Subject Alternative Name: \n DNS:nictool.com, DNS:nictool.org, DNS:www.nictool.com, DNS:www.nictool.org\n X509v3 Certificate Policies: \n Policy: 2.23.140.1.2.1\n'); + assert.deepEqual(r, ['foo.nictool.com', 'nictool.com', 'nictool.org', 'www.nictool.com', 'www.nictool.org']); + + }) + it('extracts expiration date', () => { + const r = this.socket.parse_x509_expire('foo', 'Validity\n Not Before: Mar 4 23:28:49 2017 GMT\n Not After : Mar 3 23:28:49 2023 GMT\n Subject'); + assert.deepEqual(r, new Date('2023-03-03T23:28:49.000Z')); + }) + }) +}) diff --git a/tests/transaction.js b/tests/transaction.js index f3e513004..88a61c6dc 100644 --- a/tests/transaction.js +++ b/tests/transaction.js @@ -1,28 +1,28 @@ +const assert = require('node:assert') + const fs = require('fs'); const path = require('path'); const config = require('haraka-config') const transaction = require('../transaction'); -function _set_up (done) { +const _set_up = (done) => { this.transaction = transaction.createTransaction(undefined, config.get('smtp.ini')); done() } -exports.transaction = { - setUp : _set_up, - - 'add_body_filter' (test) { +describe('transaction', () => { + beforeEach(_set_up) - test.expect(3); + it('add_body_filter', (done) => { this.transaction.add_body_filter('text/plain', (ct, enc, buf) => { // The functionality of these filter functions is tested in // haraka-email-message. This just assures the plumbing is in place. - test.ok(ct.indexOf('text/plain') === 0, "correct body part"); - test.ok(/utf-?8/i.test(enc), "correct encoding"); - test.equal(buf.toString().trim(), "Text part", "correct body contents"); + assert.ok(ct.indexOf('text/plain') === 0, "correct body part"); + assert.ok(/utf-?8/i.test(enc), "correct encoding"); + assert.equal(buf.toString().trim(), "Text part", "correct body contents"); }); [ @@ -41,18 +41,16 @@ exports.transaction = { this.transaction.add_data(line); }); this.transaction.end_data(() => { - test.done(); + done(); }); - }, - - 'regression: attachment_hooks before set_banner/add_body_filter' (test) { + }) - test.expect(2); + it('regression: attachment_hooks before set_banner/add_body_filter', (done) => { this.transaction.attachment_hooks(() => {}); this.transaction.set_banner('banner'); this.transaction.add_body_filter('', () => { - test.ok(true, "body filter called"); + assert.ok(true, "body filter called"); }); [ "Content-Type: text/plain\n", @@ -61,16 +59,16 @@ exports.transaction = { ].forEach(line => { this.transaction.add_data(line); }); + this.transaction.end_data(() => { this.transaction.message_stream.get_data(body => { - test.ok(/banner$/.test(body.toString().trim()), "banner applied"); - test.done(); - }); - }); - }, + assert.ok(/banner$/.test(body.toString().trim()), "banner applied"); + done(); + }) + }) + }) - 'correct output encoding when content in non-utf8 #2176' (test) { - const self = this; + it('correct output encoding when content in non-utf8 #2176', (done) => { // Czech panagram "Příliš žluťoučký kůň úpěl ďábelské ódy." in ISO-8859-2 encoding const message = [0x50, 0xF8, 0xED, 0x6C, 0x69, 0xB9, 0x20, 0xBE, 0x6C, 0x75, 0xBB, 0x6F, 0x76, 0xE8, 0x6B, 0xFD, 0x20, 0x6B, 0xF9, 0xF2, 0xFA, 0xEC, 0x6C, 0x20, 0xEF, 0xE2, 0x62, 0x65, 0x6C, 0x73, 0x6b, 0xE9, 0x20, 0xF3, 0x64, 0x79, 0x2E]; @@ -80,23 +78,22 @@ exports.transaction = { Buffer.from([...message, 0x0A]), // Add \n ]; - test.expect(1); this.transaction.parse_body = true; this.transaction.attachment_hooks(function () {}); - payload.forEach(function (line) { - self.transaction.add_data(line); - }); - this.transaction.end_data(function () { - self.transaction.message_stream.get_data(function (body) { - test.ok(body.includes(Buffer.from(message)), "message not damaged"); - test.done(); + for (const line of payload) { + this.transaction.add_data(line); + } + this.transaction.end_data(() => { + this.transaction.message_stream.get_data(function (body) { + assert.ok(body.includes(Buffer.from(message)), "message not damaged"); + done(); }); }); - }, + }) - 'no munging of bytes if not parsing body' (test) { + it('no munging of bytes if not parsing body', (done) => { // Czech panagram "Příliš žluťoučký kůň úpěl ďábelské ódy.\n" in ISO-8859-2 encoding const message = Buffer.from([0x50, 0xF8, 0xED, 0x6C, 0x69, 0xB9, 0x20, 0xBE, 0x6C, 0x75, 0xBB, 0x6F, 0x76, 0xE8, 0x6B, 0xFD, 0x20, 0x6B, 0xF9, 0xF2, 0xFA, 0xEC, 0x6C, 0x20, 0xEF, 0xE2, 0x62, 0x65, 0x6C, 0x73, 0x6b, 0xE9, 0x20, 0xF3, 0x64, 0x79, 0x2E, 0x0A]); @@ -106,21 +103,18 @@ exports.transaction = { message ]; - test.expect(1); - payload.forEach(line => { this.transaction.add_data(line); }); this.transaction.end_data(() => { this.transaction.message_stream.get_data(body => { - test.ok(body.includes(message), "message not damaged"); - test.done(); + assert.ok(body.includes(message), "message not damaged"); + done(); }); }); - }, + }) - 'bannering with nested mime structure' (test) { - test.expect(4); + it('bannering with nested mime structure', (done) => { this.transaction.set_banner('TEXT_BANNER', 'HTML_BANNER'); [ @@ -144,19 +138,121 @@ exports.transaction = { }); this.transaction.end_data(() => { this.transaction.message_stream.get_data(body => { - test.ok(/Hello, this is a text part/.test(body.toString()), + assert.ok(/Hello, this is a text part/.test(body.toString()), "text content comes through in final message"); - test.ok(/This is an html part/.test(body.toString()), + assert.ok(/This is an html part/.test(body.toString()), "html content comes through in final message"); - test.ok(/TEXT_BANNER/.test(body.toString()), + assert.ok(/TEXT_BANNER/.test(body.toString()), "text banner comes through in final message"); - test.ok(/HTML_BANNER/.test(body.toString()), + assert.ok(/HTML_BANNER/.test(body.toString()), "html banner comes through in final message"); - test.done(); + done(); }); }); - }, -} + }) + + describe('base64_handling', () => { + beforeEach(_set_up) + + it('varied-base64-fold-lengths-preserve-data', (done) => { + + const parsed_attachments = {}; + this.transaction.parse_body = true; + //accumulate attachment buffers. + this.transaction.attachment_hooks((ct, filename, body, stream) => { + let attachment = Buffer.alloc(0); + stream.on('data', data => { + attachment = Buffer.concat([attachment, data]); + }); + stream.on('end', () => { + parsed_attachments[filename] = attachment; + }); + }); + + const specimen_path = path.join(__dirname, 'mail_specimen', 'varied-fold-lengths-preserve-data.txt'); + write_file_data_to_transaction(this.transaction, specimen_path); + + assert.equal(this.transaction.body.children.length, 6); + + let first_attachment = null; + for (const i in parsed_attachments) { + const current_attachment = parsed_attachments[i]; + first_attachment = first_attachment || current_attachment; + // All buffers from data that was encoded with varied line lengths should + // still have the same final data. + assert.equal(true, first_attachment.equals(current_attachment), + `The buffer data for '${i}' doesn't appear to be equal to the other attachments, and is likely corrupted.`); + } + done(); + }) + + it('base64-root-html-decodes-correct-number-of-bytes', (done) => { + + this.transaction.parse_body = true; + const specimen_path = path.join(__dirname, 'mail_specimen', 'base64-root-part.txt'); + write_file_data_to_transaction(this.transaction, specimen_path); + + assert.equal(this.transaction.body.bodytext.length, 425); + done(); + }) + }) + + // Test is to ensure boundary marker just after the headers, is in-tact + // Issue: "User1990" <--abcd + // Expected: --abcd + describe('boundarymarkercorrupt_test', () => { + beforeEach(_set_up) + + // populate the same email data in transaction (self.transaction.add_data()) and + // in raw buffer, then compare + it('fix mime boundary corruption issue', (done) => { + const self = this; + let buffer = ''; + self.transaction.add_data("Content-Type: multipart/alternative; boundary=abcd\r\n"); + buffer += "Content-Type: multipart/alternative; boundary=abcd\r\n"; + self.transaction.add_data('To: "User1_firstname_middlename_lastname" ,\r\n'); + buffer += 'To: "User1_firstname_middlename_lastname" ,\r\n'; + // make sure we add headers so that it exceeds 64k bytes to expose this issue + for (let i=0;i<725;i++){ + self.transaction.add_data(` "User${i}_firstname_middlename_lastname" ,\r\n`); + buffer += ` "User${i}_firstname_middlename_lastname" ,\r\n` + } + self.transaction.add_data(' "Final User_firstname_middlename_lastname" \r\n'); + buffer += ' "Final User_firstname_middlename_lastname" \r\n'; + self.transaction.add_data('Message-ID: \r\n'); + buffer += 'Message-ID: \r\n'; + self.transaction.add_data('MIME-Version: 1.0\r\n'); + buffer += 'MIME-Version: 1.0\r\n'; + self.transaction.add_data('Date: Wed, 1 Jun 2022 16:44:39 +0530 (IST)\r\n'); + buffer += 'Date: Wed, 1 Jun 2022 16:44:39 +0530 (IST)\r\n'; + self.transaction.add_data('\r\n'); + buffer += '\r\n'; + self.transaction.add_data("--abcd\r\n"); + buffer += "--abcd\r\n"; + + [ + "Content-Type: text/plain\r\n", + "\r\n", + "Text part\r\n", + "--abcd\r\n", + "Content-Type: text/html\r\n", + "\r\n", + "

HTML part

\r\n", + "--abcd--\r\n", + ].forEach(line => { + self.transaction.add_data(line); + buffer += line; + }); + + this.transaction.end_data(function () { + self.transaction.message_stream.get_data(function (body) { + assert.ok(body.includes(buffer), "message is damaged"); + done(); + }) + }) + }) + }) +}) function write_file_data_to_transaction (test_transaction, filename) { const specimen = fs.readFileSync(filename, 'utf8'); @@ -173,106 +269,3 @@ function write_file_data_to_transaction (test_transaction, filename) { test_transaction.end_data(); } - -exports.base64_handling = { - setUp : _set_up, - - 'varied-base64-fold-lengths-preserve-data' (test) { - - const parsed_attachments = {}; - this.transaction.parse_body = true; - //accumulate attachment buffers. - this.transaction.attachment_hooks((ct, filename, body, stream) => { - let attachment = Buffer.alloc(0); - stream.on('data', data => { - attachment = Buffer.concat([attachment, data]); - }); - stream.on('end', () => { - parsed_attachments[filename] = attachment; - }); - }); - - const specimen_path = path.join(__dirname, 'mail_specimen', 'varied-fold-lengths-preserve-data.txt'); - write_file_data_to_transaction(this.transaction, specimen_path); - - test.equal(this.transaction.body.children.length, 6); - - let first_attachment = null; - for (const i in parsed_attachments) { - const current_attachment = parsed_attachments[i]; - first_attachment = first_attachment || current_attachment; - // All buffers from data that was encoded with varied line lengths should - // still have the same final data. - test.equal(true, first_attachment.equals(current_attachment), - `The buffer data for '${i}' doesn't appear to be equal to the other attachments, and is likely corrupted.`); - } - test.done(); - }, - - 'base64-root-html-decodes-correct-number-of-bytes' (test) { - - this.transaction.parse_body = true; - const specimen_path = path.join(__dirname, 'mail_specimen', 'base64-root-part.txt'); - write_file_data_to_transaction(this.transaction, specimen_path); - - test.equal(this.transaction.body.bodytext.length, 425); - test.done(); - }, -} - -// Test is to ensure boundary marker just after the headers, is in-tact -// Issue: "User1990" <--abcd -// Expected: --abcd -exports.boundarymarkercorrupt_test = { - setUp : _set_up, - - // populate the same email data in transaction (self.transaction.add_data()) and - // in raw buffer, then compare - 'fix mime boundary corruption issue' (test) { - const self = this; - let buffer = ''; - self.transaction.add_data("Content-Type: multipart/alternative; boundary=abcd\r\n"); - buffer += "Content-Type: multipart/alternative; boundary=abcd\r\n"; - self.transaction.add_data('To: "User1_firstname_middlename_lastname" ,\r\n'); - buffer += 'To: "User1_firstname_middlename_lastname" ,\r\n'; - // make sure we add headers so that it exceeds 64k bytes to expose this issue - for (let i=0;i<725;i++){ - self.transaction.add_data(` "User${i}_firstname_middlename_lastname" ,\r\n`); - buffer += ` "User${i}_firstname_middlename_lastname" ,\r\n` - } - self.transaction.add_data(' "Final User_firstname_middlename_lastname" \r\n'); - buffer += ' "Final User_firstname_middlename_lastname" \r\n'; - self.transaction.add_data('Message-ID: \r\n'); - buffer += 'Message-ID: \r\n'; - self.transaction.add_data('MIME-Version: 1.0\r\n'); - buffer += 'MIME-Version: 1.0\r\n'; - self.transaction.add_data('Date: Wed, 1 Jun 2022 16:44:39 +0530 (IST)\r\n'); - buffer += 'Date: Wed, 1 Jun 2022 16:44:39 +0530 (IST)\r\n'; - self.transaction.add_data('\r\n'); - buffer += '\r\n'; - self.transaction.add_data("--abcd\r\n"); - buffer += "--abcd\r\n"; - - [ - "Content-Type: text/plain\r\n", - "\r\n", - "Text part\r\n", - "--abcd\r\n", - "Content-Type: text/html\r\n", - "\r\n", - "

HTML part

\r\n", - "--abcd--\r\n", - ].forEach(line => { - self.transaction.add_data(line); - buffer += line; - }); - - this.transaction.end_data(function () { - self.transaction.message_stream.get_data(function (body) { - test.ok(body.includes(buffer), "message is damaged"); - test.done(); - }); - }); - }, -} -