Skip to content

Commit

Permalink
doc(README): added more example usage
Browse files Browse the repository at this point in the history
- transaction: sync with haraka/Haraka
- connection: import more from haraka/Haraka/connection
- test(conn): expect more connection properties
  • Loading branch information
msimerson committed Apr 14, 2024
1 parent 8b4a1a6 commit 47c7f49
Show file tree
Hide file tree
Showing 12 changed files with 440 additions and 163 deletions.
2 changes: 1 addition & 1 deletion .release
Submodule .release updated 1 files
+7 −6 start.sh
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

### Unreleased

### [1.3.6] - 2024-04-09
### [1.3.6] - 2024-04-14

- transaction: sync with haraka/Haraka
- connection: import more from haraka/Haraka/connection
- test(conn): expect more connection properties
- doc(CONTRIBUTING): added
- doc(README): added example setup

### [1.3.5] - 2024-04-07

Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@

# Contributors

This handcrafted artisinal software is brought to you by:

| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=msimerson">60</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/1432035?v=4"><br><a href="https://github.com/kcberg">kcberg</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=kcberg">1</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=baudehlo">1</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=smfreegard">1</a>)| <img height="80" src="https://avatars.githubusercontent.com/u/203240?v=4"><br><a href="https://github.com/lnedry">lnedry</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=lnedry">1</a>)|
| :---: | :---: | :---: | :---: | :---: |
| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=msimerson">61</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1432035?v=4"><br><a href="https://github.com/kcberg">kcberg</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=kcberg">1</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=baudehlo">1</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=smfreegard">1</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/203240?v=4"><br><a href="https://github.com/lnedry">lnedry</a> (<a href="https://github.com/haraka/haraka-plugin-access/commits?author=lnedry">1</a>) |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |

<sub>this file is maintained by [.release](https://github.com/msimerson/.release)</sub>
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,30 @@

Fixtures for testing Haraka and plugins

# Usage
## Usage

`const fixtures = require('haraka-test-fixtures');`
```js
const fixtures = require('haraka-test-fixtures')
```

# Exports the following fixture types:
### A common pattern

```js
beforeEach(() => {
this.plugin = new fixtures.plugin('pluginName')

this.connection = fixtures.connection.createConnection()
this.connection.init_transaction()
})

describe('pluginName', () => {
it('registers', () => {
this.plugin.register()
})
})
```

## Exports the following fixture types:

- connection
- line_socket
Expand Down
254 changes: 217 additions & 37 deletions lib/connection.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,256 @@
const config = require('haraka-config')
const constants = require('haraka-constants')
const Notes = require('haraka-notes')
const ResultStore = require('haraka-results')
const utils = require('haraka-utils')

const logger = require('./logger')
const transaction = require('./transaction')

const cfg = config.get('smtp.ini', {
booleans: [
'+main.smtputf8',
'+headers.add_received',
'+headers.clean_auth_results',
],
})
const states = constants.connection.state

class Connection {
constructor(client, server) {
constructor(client, server, cfg) {
this.client = client
this.server = server
this.relaying = false
this.local = {}
this.cfg = cfg

this.local = {
ip: null,
port: null,
host: 'haraka-test.example.com',
info: 'Haraka',
}
this.remote = {
ip: '127.0.0.1',
port: null,
host: null,
info: null,
closed: false,
is_private: false,
is_local: false,
}
this.hello = {
host: null,
verb: null,
}
this.tls = {
enabled: false,
advertised: false,
verified: false,
cipher: {},
}
this.tls = {}
this.hello = {}
this.proxy = {
allowed: false,
ip: null,
type: null,
timer: null,
}
this.set('tls', 'enabled', !!server.has_tls)

this.current_data = null
this.current_line = null
this.state = states.PAUSE
this.encoding = 'utf8'
this.prev_state = null
this.loop_code = null
this.loop_msg = null
this.uuid = utils.uuid()
this.notes = new Notes()
this.transaction = null
this.tran_count = 0
this.capabilities = null
this.ehlo_hello_message = 'Haraka Test is at your service.'
this.connection_close_message = 'closing test connection.'
this.banner_includes_uuid = true
this.deny_includes_uuid = true
this.early_talker = false
this.pipelining = false
this._relaying = false
this.esmtp = false
this.last_response = null
this.hooks_to_run = []
this.start_time = Date.now()
this.last_reject = ''
this.max_bytes = 0
this.max_mime_parts = 1000
this.totalbytes = 0
this.rcpt_count = {
accept: 0,
tempfail: 0,
reject: 0,
}
this.msg_count = {
accept: 0,
tempfail: 0,
reject: 0,
}
this.max_line_length = 512
this.max_data_line_length = 992
this.results = new ResultStore(this)
this.errors = 0
this.last_rcpt_msg = null
this.hook = null

logger.add_log_methods(this, 'mock-connection')
Connection.setupClient(this)
}
auth_results(message) {}
respond(code, msg, func) {
return func()
}
init_transaction(done) {
this.transaction = new transaction.createTransaction(null, cfg)
this.transaction.results = new ResultStore(this)
if (done) done()

static setupClient(self) {
if (Object.keys(self.client).length === 0) return
const ip = self.client.remoteAddress
if (!ip) {
self.logdebug('setupClient got no IP address for this connection!')
self.client.destroy()
return
}

const local_addr = self.server.address()
self.set(
'local',
'ip',
ipaddr.process(self.client.localAddress || local_addr.address).toString(),

Check warning on line 112 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'ipaddr' is not defined

Check warning on line 112 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'ipaddr' is not defined
)
self.set('local', 'port', self.client.localPort || local_addr.port)
self.results.add({ name: 'local' }, self.local)

self.set('remote', 'ip', ipaddr.process(ip).toString())

Check warning on line 117 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'ipaddr' is not defined

Check warning on line 117 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'ipaddr' is not defined
self.set('remote', 'port', self.client.remotePort)
self.results.add({ name: 'remote' }, self.remote)

self.lognotice('connect', {
ip: self.remote.ip,
port: self.remote.port,
local_ip: self.local.ip,
local_port: self.local.port,
})

if (!self.client.on) return

const log_data = { ip: self.remote.ip }
if (self.remote.host) log_data.host = self.remote.host

self.client.on('end', () => {
if (self.state >= states.DISCONNECTING) return
self.remote.closed = true
self.loginfo('client half closed connection', log_data)
self.fail()
})

self.client.on('close', (has_error) => {

Check warning on line 140 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'has_error' is defined but never used

Check warning on line 140 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'has_error' is defined but never used
if (self.state >= states.DISCONNECTING) return
self.remote.closed = true
self.loginfo('client dropped connection', log_data)
self.fail()
})

self.client.on('error', (err) => {
if (self.state >= states.DISCONNECTING) return
self.loginfo(`client connection error: ${err}`, log_data)
self.fail()
})

self.client.on('timeout', () => {
// FIN has sent, when timeout just destroy socket
if (self.state >= states.DISCONNECTED) {
self.client.destroy()
self.loginfo(`timeout, destroy socket (state:${self.state})`)
return
}
if (self.state >= states.DISCONNECTING) return
self.respond(421, 'timeout', () => {
self.fail('client connection timed out', log_data)
})
})

self.client.on('data', (data) => {
self.process_data(data)
})

plugins.run_hooks('connect_init', self)

Check warning on line 170 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'plugins' is not defined

Check warning on line 170 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'plugins' is not defined
}
reset_transaction(done) {
if (this.transaction && this.transaction.resetting === false) {
this.transaction.resetting = true
} else {
this.transaction = null

setTLS(obj) {
this.set('hello', 'host', undefined)
this.set('tls', 'enabled', true)
for (const t of ['cipher', 'verified', 'verifyError', 'peerCertificate']) {
if (obj[t] === undefined) return
this.set('tls', t, obj[t])
}
if (done) done()
}

set(prop_str, val) {
if (arguments.length === 3) {
prop_str = `${arguments[0]}.${arguments[1]}`
val = arguments[2]
}

const segments = prop_str.split('.')
let dest = this
while (segments.length > 1) {
if (!dest[segments[0]]) dest[segments[0]] = {}
dest = dest[segments.shift()]
const path_parts = prop_str.split('.')
let loc = this
for (let i = 0; i < path_parts.length; i++) {
const part = path_parts[i]
if (part === '__proto__' || part === 'constructor') continue

// while another part remains
if (i < path_parts.length - 1) {
if (loc[part] === undefined) loc[part] = {} // initialize
loc = loc[part] // descend
continue
}

// last part, so assign the value
loc[part] = val
}
dest[segments[0]] = val
}

get(prop_str) {
return prop_str.split('.').reduce((prev, curr) => {
return prev ? prev[curr] : undefined
}, this)
}

set relaying(val) {
if (this.transaction) {
this.transaction._relaying = val
} else {
this._relaying = val
}
}
get relaying() {
if (this.transaction && '_relaying' in this.transaction)
return this.transaction._relaying
return this._relaying
}
auth_results(message) {}

Check warning on line 224 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'message' is defined but never used

Check warning on line 224 in lib/connection.js

View workflow job for this annotation

GitHub Actions / lint / lint

'message' is defined but never used
respond(code, msg, func) {
return func()
}
init_transaction(done) {
this.transaction = new transaction.createTransaction(null, this.cfg)
this.transaction.results = new ResultStore(this)
if (done) done()
}
reset_transaction(done) {
if (this.transaction && this.transaction.resetting === false) {
this.transaction.resetting = true
} else {
this.transaction = null
}
if (done) done()
}
}

exports.Connection = Connection

exports.createConnection = function (client, server) {
if (typeof client === 'undefined') client = {}
if (typeof server === 'undefined') server = {}

return new Connection(client, server)
exports.createConnection = function (client = {}, server = {}, cfg = {}) {
if (!cfg || Object.keys(cfg).length === 0) {
cfg = config.get('smtp.ini', {
booleans: [
'+main.smtputf8',
'+headers.add_received',
'+headers.clean_auth_results',
],
})
}
return new Connection(client, server, cfg)
}
Loading

0 comments on commit 47c7f49

Please sign in to comment.