This repository has been archived by the owner on Apr 3, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(bounces): pull bounce logic into separate module
- Loading branch information
1 parent
b06b0da
commit 48d7625
Showing
15 changed files
with
270 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
'use strict' | ||
|
||
const error = require('./error') | ||
const P = require('./promise') | ||
|
||
module.exports = (config, db) => { | ||
const configBounces = config.smtp && config.smtp.bounces || {} | ||
const BOUNCES_ENABLED = !! configBounces.enabled | ||
const MAX_HARD = configBounces.hard && configBounces.hard.max || 0 | ||
const MAX_SOFT = configBounces.soft && configBounces.soft.max || 0 | ||
const MAX_COMPLAINT = configBounces.complaint && configBounces.complaint.max || 0 | ||
const DURATION_HARD = configBounces.hard && configBounces.hard.duration || Infinity | ||
const DURATION_SOFT = configBounces.soft && configBounces.soft.duration || Infinity | ||
const DURATION_COMPLAINT = configBounces.complaint && configBounces.complaint.duration || Infinity | ||
const BOUNCE_TYPE_HARD = 1 | ||
const BOUNCE_TYPE_SOFT = 2 | ||
const BOUNCE_TYPE_COMPLAINT = 3 | ||
|
||
const freeze = Object.freeze | ||
const BOUNCE_RULES = freeze({ | ||
[BOUNCE_TYPE_HARD]: freeze({ | ||
duration: DURATION_HARD, | ||
error: error.emailBouncedHard, | ||
max: MAX_HARD | ||
}), | ||
[BOUNCE_TYPE_COMPLAINT]: freeze({ | ||
duration: DURATION_COMPLAINT, | ||
error: error.emailComplaint, | ||
max: MAX_COMPLAINT | ||
}), | ||
[BOUNCE_TYPE_SOFT]: freeze({ | ||
duration: DURATION_SOFT, | ||
error: error.emailBouncedSoft, | ||
max: MAX_SOFT | ||
}) | ||
}) | ||
|
||
function checkBounces(email) { | ||
return db.emailBounces(email) | ||
.then(bounces => { | ||
const counts = { | ||
[BOUNCE_TYPE_HARD]: 0, | ||
[BOUNCE_TYPE_COMPLAINT]: 0, | ||
[BOUNCE_TYPE_SOFT]: 0 | ||
} | ||
const now = Date.now() | ||
bounces.forEach(bounce => { | ||
const type = bounce.bounceType | ||
const ruleSet = BOUNCE_RULES[type] | ||
if (ruleSet) { | ||
if (bounce.createdAt > now - ruleSet.duration) { | ||
counts[type]++ | ||
if (counts[type] > ruleSet.max) { | ||
throw ruleSet.error() | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
function disabled() { | ||
return P.resolve() | ||
} | ||
|
||
return { | ||
check: BOUNCES_ENABLED ? checkBounces : disabled | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
'use strict' | ||
|
||
const ROOT_DIR = '../../..' | ||
|
||
const assert = require('insist') | ||
const config = require(`${ROOT_DIR}/config`).getProperties() | ||
const createBounces = require(`${ROOT_DIR}/lib/bounces`) | ||
const error = require(`${ROOT_DIR}/lib/error`) | ||
const P = require('bluebird') | ||
const sinon = require('sinon') | ||
|
||
const EMAIL = Math.random() + '@example.test' | ||
const BOUNCE_TYPE_HARD = 1 | ||
const BOUNCE_TYPE_COMPLAINT = 3 | ||
|
||
const NOW = Date.now() | ||
|
||
describe('bounces', () => { | ||
|
||
it('succeeds if bounces not over limit', () => { | ||
const db = { | ||
emailBounces: sinon.spy(() => P.resolve([])) | ||
} | ||
return createBounces(config, db).check(EMAIL) | ||
.then(() => { | ||
assert.equal(db.emailBounces.callCount, 1) | ||
}) | ||
}) | ||
|
||
it('error if complaints over limit', () => { | ||
const conf = Object.assign({}, config) | ||
conf.smtp = { | ||
bounces: { | ||
enabled: true, | ||
complaint: { | ||
max: 0 | ||
} | ||
} | ||
} | ||
const db = { | ||
emailBounces: sinon.spy(() => P.resolve([ | ||
{ | ||
bounceType: BOUNCE_TYPE_COMPLAINT, | ||
createdAt: NOW | ||
} | ||
])) | ||
} | ||
return createBounces(conf, db).check(EMAIL) | ||
.then( | ||
() => assert(false), | ||
e => { | ||
assert.equal(db.emailBounces.callCount, 1) | ||
assert.equal(e.errno, error.ERRNO.BOUNCE_COMPLAINT) | ||
} | ||
) | ||
}) | ||
|
||
it('error if hard bounces over limit', () => { | ||
const conf = Object.assign({}, config) | ||
conf.smtp = { | ||
bounces: { | ||
enabled: true, | ||
hard: { | ||
max: 0 | ||
} | ||
} | ||
} | ||
const db = { | ||
emailBounces: sinon.spy(() => P.resolve([ | ||
{ | ||
bounceType: BOUNCE_TYPE_HARD, | ||
createdAt: NOW | ||
} | ||
])) | ||
} | ||
return createBounces(conf, db).check(EMAIL) | ||
.then( | ||
() => assert(false), | ||
e => { | ||
assert.equal(db.emailBounces.callCount, 1) | ||
assert.equal(e.errno, error.ERRNO.BOUNCE_HARD) | ||
} | ||
) | ||
}) | ||
|
||
it('does not error if not enough bounces in duration', () => { | ||
const conf = Object.assign({}, config) | ||
conf.smtp = { | ||
bounces: { | ||
enabled: true, | ||
hard: { | ||
max: 0, | ||
duration: 5000 | ||
} | ||
} | ||
} | ||
const db = { | ||
emailBounces: sinon.spy(() => P.resolve([ | ||
{ | ||
bounceType: BOUNCE_TYPE_HARD, | ||
createdAt: Date.now() - 20000 | ||
} | ||
])) | ||
} | ||
return createBounces(conf, db).check(EMAIL) | ||
.then(() => { | ||
assert.equal(db.emailBounces.callCount, 1) | ||
}) | ||
}) | ||
|
||
|
||
describe('disabled', () => { | ||
it('does not call bounces.check if disabled', () => { | ||
const conf = Object.assign({}, config) | ||
conf.smtp = { | ||
bounces: { | ||
enabled: false | ||
} | ||
} | ||
const db = { | ||
emailBounces: sinon.spy(() => P.resolve([ | ||
{ | ||
bounceType: BOUNCE_TYPE_HARD, | ||
createdAt: Date.now() - 20000 | ||
} | ||
])) | ||
} | ||
assert.equal(db.emailBounces.callCount, 0) | ||
return createBounces(conf, db).check(EMAIL) | ||
.then(() => { | ||
assert.equal(db.emailBounces.callCount, 0) | ||
}) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.