Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Tomas Bures 2017-05-18 22:46:53 +02:00
commit 62cc881fd4
29 changed files with 1722 additions and 34 deletions

2
.gitignore vendored
View file

@ -3,8 +3,10 @@ npm-debug.log
.DS_Store
config/development.*
config/production.*
config/test.*
workers/reports/config/development.*
workers/reports/config/production.*
workers/reports/config/test.*
dump.rdb
# generate POT file every time you want to update your PO file

View file

@ -9,7 +9,7 @@ module.exports = function (grunt) {
},
nodeunit: {
all: ['test/**/*-test.js']
all: ['test/nodeunit/**/*-test.js']
},
jsxgettext: {

View file

@ -1,9 +1,11 @@
# Mailtrain
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v5+) and MySQL (v5.5+ or MariaDB).
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v7+) and MySQL (v5.5+ or MariaDB).
![](http://mailtrain.org/mailtrain.png)
> Mailtrain requires at least **Node.js v7**. If you want to use an older version of Node.js then you should use version v1.24 of Mailtrain. You can either download it [here](https://github.com/Mailtrain-org/mailtrain/archive/v1.24.0.zip) or if using git then run `git checkout v1.24.0` before starting it
## Features
Mailtrain supports subscriber list management, list segmentation, custom fields, email templates, large CSV list import files, etc.
@ -45,7 +47,7 @@ Check out [ZoneMTA](https://github.com/zone-eu/zone-mta) as an alternative self
## Requirements
* Nodejs v6+
* Nodejs v7+
* MySQL v5.5 or MariaDB
* Redis. Optional, disabled by default. Used for session storage and for caching state between multiple processes. If you do not have Redis enabled then you can only use a single sender process

5
app.js
View file

@ -183,6 +183,11 @@ app.use((req, res, next) => {
res.locals.customStyles = config.customstyles || [];
res.locals.customScripts = config.customscripts || [];
let bodyClasses = [];
app.get('env') === 'test' && bodyClasses.push('page--' + (req.path.substring(1).replace(/\//g, '--') || 'home'));
req.user && bodyClasses.push('logged-in user-' + req.user.username);
res.locals.bodyClass = bodyClasses.join(' ');
settingsModel.list(['ua_code', 'shoutout'], (err, configItems) => {
if (err) {
return next(err);

View file

@ -110,16 +110,6 @@ host="0.0.0.0"
# VERP hostname is in the same domain as the From address.
# disablesenderheader=true
[testserver]
# Starts a vanity server that redirects all mail to /dev/null
# Mostly needed for local development
enabled=false
port=5587
host="0.0.0.0"
username="testuser"
password="testpass"
logger=false
[ldap]
# enable to use ldap user backend
enabled=false
@ -177,3 +167,17 @@ templates=[["demo", "Demo Template"]]
# The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted,
# then it's safer to switch off the reporting functionality below.
enabled=false
[testserver]
# Starts a vanity server that redirects all mail to /dev/null
# Mostly needed for local development
enabled=false
port=5587
mailboxserverport=3001
host="0.0.0.0"
username="testuser"
password="testpass"
logger=false
[seleniumwebdriver]
browser="phantomjs"

View file

@ -107,13 +107,14 @@ function getSql(path, data, callback) {
if (err) {
return callback(err);
}
let renderer = Handlebars.compile(source);
return callback(null, renderer(data || {}));
const rendered = data ? Handlebars.compile(source)(data) : source;
return callback(null, rendered);
});
}
function runInitial(callback) {
let fname = process.env.DB_FROM_START ? 'base.sql' : 'mailtrain.sql';
let dump = process.env.NODE_ENV === 'test' ? 'mailtrain-test.sql' : 'mailtrain.sql';
let fname = process.env.DB_FROM_START ? 'base.sql' : dump;
let path = pathlib.join(__dirname, '..', 'setup', 'sql', fname);
log.info('sql', 'Loading tables from %s', fname);
applyUpdate({

View file

@ -8,12 +8,17 @@
"test": "grunt",
"start": "node index.js",
"sqlinit": "node setup/sql/init.js",
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain.sql",
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain${DUMP_NAME_SUFFIX}.sql",
"sqldrop": "node setup/sql/drop.js",
"sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump",
"langs:hbs": "jsxgettext -L handlebars -k translate -o langs/hbs.pot views/layout.hbs views/index.hbs",
"langs:js": "jsxgettext -o languages/js.pot routes/index.js",
"langs": "npm run langs:hbs && npm run langs:js"
"langs": "npm run langs:hbs && npm run langs:js",
"sqldumptest": "NODE_ENV=test DUMP_NAME_SUFFIX=-test npm run sqldump",
"sqlresettest": "NODE_ENV=test npm run sqldrop && NODE_ENV=test npm run sqlinit",
"starttest": "NODE_ENV=test node index.js",
"_e2e": "NODE_ENV=test mocha test/e2e/index.js",
"e2e": "npm run sqlresettest && npm run _e2e"
},
"repository": {
"type": "git",
@ -26,12 +31,18 @@
"node": ">=5.0.0"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"chai": "^3.5.0",
"eslint-config-nodemailer": "^1.0.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^19.0.0",
"jsxgettext-andris": "^0.9.0-patch.1"
"jsxgettext-andris": "^0.9.0-patch.1",
"mailparser": "^2.0.5",
"mocha": "^3.3.0",
"phantomjs": "^2.1.7",
"selenium-webdriver": "^3.4.0"
},
"optionalDependencies": {
"posix": "^4.1.1"

View file

@ -4,12 +4,37 @@ let log = require('npmlog');
let config = require('config');
let crypto = require('crypto');
let humanize = require('humanize');
let http = require('http');
let SMTPServer = require('smtp-server').SMTPServer;
let simpleParser = require('mailparser').simpleParser;
let totalMessages = 0;
let received = 0;
let mailstore = {
accounts: {},
saveMessage(address, message) {
if (!this.accounts[address]) {
this.accounts[address] = [];
}
this.accounts[address].push(message);
},
getMail(address, callback) {
if (!this.accounts[address] || this.accounts[address].length === 0) {
let err = new Error('No mail for ' + address);
err.status = 404;
return callback(err);
}
simpleParser(this.accounts[address].shift(), (err, mail) => {
if (err) {
return callback(err.message || err);
}
callback(null, mail);
});
}
};
// Setup server
let server = new SMTPServer({
@ -74,8 +99,12 @@ let server = new SMTPServer({
// Handle message stream
onData: (stream, session, callback) => {
let hash = crypto.createHash('md5');
let message = '';
stream.on('data', chunk => {
hash.update(chunk);
if (/^keep/i.test(session.envelope.rcptTo[0].address)) {
message += chunk;
}
});
stream.on('end', () => {
let err;
@ -84,6 +113,12 @@ let server = new SMTPServer({
err.responseCode = 552;
return callback(err);
}
// Store message for e2e tests
if (/^keep/i.test(session.envelope.rcptTo[0].address)) {
mailstore.saveMessage(session.envelope.rcptTo[0].address, message);
}
received++;
callback(null, 'Message queued as ' + hash.digest('hex')); // accept the message once the stream is ended
});
@ -94,6 +129,41 @@ server.on('error', err => {
log.error('Test SMTP', err.stack);
});
let mailBoxServer = http.createServer((req, res) => {
let renderer = data => (
'<!doctype html><html><head><title>' + data.title + '</title></head><body>' + data.body + '</body></html>'
);
let address = req.url.substring(1);
mailstore.getMail(address, (err, mail) => {
if (err) {
let html = renderer({
title: 'error',
body: err.message || err
});
res.writeHead(err.status || 500, { 'Content-Type': 'text/html' });
return res.end(html);
}
let html = mail.html || renderer({
title: 'error',
body: 'This mail has no HTML part'
});
// https://nodemailer.com/extras/mailparser/#mail-object
delete mail.html;
delete mail.textAsHtml;
delete mail.attachments;
let script = '<script> var mailObject = ' + JSON.stringify(mail) + '; console.log(mailObject); </script>';
html = html.replace(/<\/body\b/i, match => script + match);
html = html.replace(/target="_blank"/g, 'target="_self"');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
});
});
module.exports = callback => {
if (config.testserver.enabled) {
server.listen(config.testserver.port, config.testserver.host, () => {
@ -112,7 +182,10 @@ module.exports = callback => {
}
}, 60 * 1000);
setImmediate(callback);
mailBoxServer.listen(config.testserver.mailboxserverport, config.testserver.host, () => {
log.info('Test SMTP', 'Mail Box Server listening on port %s', config.testserver.mailboxserverport);
setImmediate(callback);
});
});
} else {
setImmediate(callback);

View file

@ -12,7 +12,7 @@ set -e
yum -y install epel-release
curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
curl --silent --location https://rpm.nodesource.com/setup_7.x | bash -
yum -y install mariadb-server nodejs ImageMagick git python redis pwgen bind-utils gcc-c++ make
systemctl start mariadb
@ -225,4 +225,3 @@ systemctl start zone-mta.service
systemctl start mailtrain.service
echo "Success! Open http://$HOSTNAME/ and log in as admin:test";

View file

@ -12,7 +12,7 @@ set -e
export DEBIAN_FRONTEND=noninteractive
curl -sL https://deb.nodesource.com/setup_6.x | bash -
curl -sL https://deb.nodesource.com/setup_7.x | bash -
apt-get -q -y install mariadb-server pwgen nodejs imagemagick git ufw build-essential dnsutils python software-properties-common
apt-add-repository -y ppa:chris-lea/redis-server

View file

@ -1,17 +1,23 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
console.log('This script does not run in production'); // eslint-disable-line no-console
process.exit(1);
}
let config = require('config');
let spawn = require('child_process').spawn;
let log = require('npmlog');
let path = require('path');
let fs = require('fs');
log.level = 'verbose';
if (process.env.NODE_ENV === 'production') {
log.error('sqldrop', 'This script does not run in production');
process.exit(1);
}
if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) {
log.error('sqldrop', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present');
process.exit(1);
}
function createDump(callback) {
let cmd = spawn(path.join(__dirname, 'drop.sh'), [], {
env: {

View file

@ -1,15 +1,22 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
console.log('This script does not run in production'); // eslint-disable-line no-console
process.exit(1);
}
let dbcheck = require('../../lib/dbcheck');
let log = require('npmlog');
let path = require('path');
let fs = require('fs');
log.level = 'verbose';
if (process.env.NODE_ENV === 'production') {
log.error('sqlinit', 'This script does not run in production');
process.exit(1);
}
if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) {
log.error('sqlinit', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present');
process.exit(1);
}
dbcheck(err => {
if (err) {
log.error('DB', err);

1023
setup/sql/mailtrain-test.sql Normal file

File diff suppressed because it is too large Load diff

11
test/e2e/.eslintrc Normal file
View file

@ -0,0 +1,11 @@
{
"parser": "babel-eslint",
"rules": {
"strict": 0,
"no-invalid-this": 0,
"no-unused-expressions": 0
},
"env": {
"mocha": true
}
}

44
test/e2e/README.md Normal file
View file

@ -0,0 +1,44 @@
# e2e Tests
Running e2e tests requires Node 7.6 or later and a dedicated test database. It uses mocha, selenium-webdriver and phantomjs.
## Installation
These e2e tests have to be performed against predefined resources (e.g. lists, users, etc.) and therefore a dedicated test database and test config is required.
Both can be created by running `sudo sh test/e2e/install.sh` from within your mailtrain directory. This creates a MYSQL user and database called `mailtrain_test`, and generates the required `config/test.toml`.
## Running e2e Tests
For tests to succeed Mailtrian must be started in `test` mode on port 3000 (as http://localhost:3000/ is the predefined service url). The tests itself have to be started in a second Terminal window.
1. Start Mailtrain with `npm run starttest`
2. Start e2e tests with `npm run e2e`
## Using Different Browsers
By default e2e tests use `phantomjs`. If you want to use a different browser you need to install its driver and adjust your `config/test.toml`.
* Install the `firefox` driver with `npm install geckodriver`
* Install the `chrome` driver with `npm install chromedriver`
* Other drivers can be found [here](https://seleniumhq.github.io/selenium/docs/api/javascript/)
Then adjust your config:
```
[seleniumwebdriver]
browser="firefox"
```
Current Firefox issue (and patch): https://github.com/mozilla/geckodriver/issues/683
## Writing e2e Tests
You should spend your time on features rather than writing tests, yet in some cases, like for example the subscription process, manual testing is just silly. You best get started by reading the current test suites, or just open an issue describing the scenario you want to get tested.
Available commands:
* `npm run sqldumptest` - exports the test DB to `setup/sql/mailtrain-test.sql`
* `npm run sqlresettest` - drops all tables then loads `setup/sql/mailtrain-test.sql`
* `npm run _e2e` - just runs e2e tests
* `npm run e2e` - runs `sqlresettest` then `_e2e`

View file

@ -0,0 +1,31 @@
'use strict';
const config = require('config');
module.exports = {
app: config,
baseUrl: 'http://localhost:' + config.www.port,
users: {
admin: {
username: 'admin',
password: 'test'
}
},
lists: {
one: {
id: 1,
cid: 'Hkj1vCoJb',
publicSubscribe: 1,
unsubscriptionMode: 0
}
},
settings: {
'service-url' : 'http://localhost:' + config.www.port + '/',
'default-homepage': 'https://mailtrain.org',
'smtp-hostname': config.testserver.host,
'smtp-port': config.testserver.port,
'smtp-encryption': 'NONE',
'smtp-user': config.testserver.username,
'smtp-pass': config.testserver.password
}
};

View file

@ -0,0 +1,15 @@
'use strict';
const config = require('./config');
const webdriver = require('selenium-webdriver');
const driver = new webdriver.Builder()
.forBrowser(config.app.seleniumwebdriver.browser || 'phantomjs')
.build();
if (global.USE_SHARED_DRIVER === true) {
driver.originalQuit = driver.quit;
driver.quit = () => {};
}
module.exports = driver;

View file

@ -0,0 +1,21 @@
'use strict';
const config = require('./config');
const log = require('npmlog');
const path = require('path');
const fs = require('fs');
if (process.env.NODE_ENV !== 'test' || !fs.existsSync(path.join(__dirname, '..', '..', '..', 'config', 'test.toml'))) {
log.error('e2e', 'This script only runs in test and config/test.toml (i.e. a dedicated test database) is present');
process.exit(1);
}
if (config.app.testserver.enabled !== true) {
log.error('e2e', 'This script only runs if the testserver is enabled. Check config/test.toml');
process.exit(1);
}
if (config.app.www.port !== 3000) {
log.error('e2e', 'This script requires Mailtrain to be running on port 3000. Check config/test.toml');
process.exit(1);
}

36
test/e2e/index.js Normal file
View file

@ -0,0 +1,36 @@
'use strict';
require('./helpers/exit-unless-test');
global.USE_SHARED_DRIVER = true;
const driver = require('./helpers/driver');
const only = 'only';
const skip = 'skip';
let tests = [
['tests/login'],
['tests/subscription']
];
tests = tests.filter(t => t[1] !== skip);
if (tests.some(t => t[1] === only)) {
tests = tests.filter(t => t[1] === only);
}
describe('e2e', function() {
this.timeout(10000);
tests.forEach(t => {
describe(t[0], () => {
require('./' + t[0]); // eslint-disable-line global-require
});
});
after(() => driver.originalQuit());
});

36
test/e2e/install.sh Normal file
View file

@ -0,0 +1,36 @@
#!/bin/bash
# This installation script works on Ubuntu 14.04 and 16.04
# Run as root!
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit 1
fi
set -e
export DEBIAN_FRONTEND=noninteractive
MYSQL_PASSWORD=`pwgen 12 -1`
# Setup MySQL user for Mailtrain Tests
mysql -u root -e "CREATE USER 'mailtrain_test'@'localhost' IDENTIFIED BY '$MYSQL_PASSWORD';"
mysql -u root -e "GRANT ALL PRIVILEGES ON mailtrain_test.* TO 'mailtrain_test'@'localhost';"
mysql -u mailtrain_test --password="$MYSQL_PASSWORD" -e "CREATE database mailtrain_test;"
# Setup installation configuration
cat >> config/test.toml <<EOT
[www]
port=3000
[mysql]
user="mailtrain_test"
password="$MYSQL_PASSWORD"
database="mailtrain_test"
[testserver]
enabled=true
[seleniumwebdriver]
browser="phantomjs"
EOT
echo "Success! The test database has been created.";

View file

@ -0,0 +1,21 @@
'use strict';
const page = require('./page');
module.exports = driver => Object.assign(page(driver), {
elementToWaitFor: 'alert',
elements: {
alert: 'div.alert:not(.js-warning)'
},
getText() {
return this.element('alert').getText();
},
clear() {
return this.driver.executeScript(`
var elements = document.getElementsByClassName('alert');
while(elements.length > 0){
elements[0].parentNode.removeChild(elements[0]);
}
`);
}
});

View file

@ -0,0 +1,11 @@
'use strict';
const page = require('./page');
module.exports = driver => Object.assign(page(driver), {
url: '/',
elementToWaitFor: 'body',
elements: {
body: 'body.page--home'
}
});

View file

@ -0,0 +1,55 @@
'use strict';
const config = require('../helpers/config');
const webdriver = require('selenium-webdriver');
const By = webdriver.By;
const until = webdriver.until;
module.exports = driver => ({
driver,
elements: {},
element(key) {
return this.driver.findElement(By.css(this.elements[key] || key));
},
navigate(path) {
this.driver.navigate().to(config.baseUrl + (path || this.url));
return this.waitUntilVisible();
},
waitUntilVisible() {
let selector = this.elements[this.elementToWaitFor];
if (!selector && this.url) {
selector = 'body.page--' + (this.url.substring(1).replace(/\//g, '--') || 'home');
}
return selector ? this.driver.wait(until.elementLocated(By.css(selector))) : this.driver.sleep(1000);
},
submit() {
return this.element('submitButton').click();
},
click(key) {
return this.element(key).click();
},
getText(key) {
return this.element(key).getText();
},
getValue(key) {
return this.element(key).getAttribute('value');
},
setValue(key, value) {
return this.element(key).sendKeys(value);
},
containsText(str) {
// let text = await driver.findElement({ css: 'body' }).getText();
return this.driver.executeScript(`
return (document.documentElement.textContent || document.documentElement.innerText).indexOf('${str}') > -1;
`);
}
});

View file

@ -0,0 +1,84 @@
'use strict';
const config = require('../helpers/config');
const page = require('./page');
const web = {
enterEmail(value) {
this.element('emailInput').clear();
return this.element('emailInput').sendKeys(value);
}
};
const mail = {
navigate(address) {
this.driver.sleep(100);
this.driver.navigate().to(`http://localhost:${config.app.testserver.mailboxserverport}/${address}`);
return this.waitUntilVisible();
}
};
module.exports = (driver, list) => ({
webSubscribe: Object.assign(page(driver), web, {
url: `/subscription/${list.cid}`,
elementToWaitFor: 'form',
elements: {
form: `form[action="/subscription/${list.cid}/subscribe"]`,
emailInput: '#main-form input[name="email"]',
submitButton: 'a[href="#submit"]'
}
}),
webConfirmSubscriptionNotice: Object.assign(page(driver), web, {
url: `/subscription/${list.cid}/confirm-notice`,
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: `a[href="${config.settings['default-homepage']}"]`
}
}),
mailConfirmSubscription: Object.assign(page(driver), mail, {
elementToWaitFor: 'confirmLink',
elements: {
confirmLink: `a[href^="${config.settings['service-url']}subscription/subscribe/"]`
}
}),
webSubscribedNotice: Object.assign(page(driver), web, {
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: 'a[href^="https://mailtrain.org"]'
}
}),
mailSubscriptionConfirmed: Object.assign(page(driver), mail, {
elementToWaitFor: 'unsubscribeLink',
elements: {
unsubscribeLink: 'a[href*="/unsubscribe/"]',
manageLink: 'a[href*="/manage/"]'
}
}),
webUnsubscribe: Object.assign(page(driver), web, {
elementToWaitFor: 'submitButton',
elements: {
submitButton: 'a[href="#submit"]'
}
}),
webUnsubscribedNotice: Object.assign(page(driver), web, {
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: 'a[href^="https://mailtrain.org"]'
}
}),
mailUnsubscriptionConfirmed: Object.assign(page(driver), mail, {
elementToWaitFor: 'resubscribeLink',
elements: {
resubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}"]`
}
})
});

View file

@ -0,0 +1,32 @@
'use strict';
const page = require('./page');
module.exports = driver => ({
login: Object.assign(page(driver), {
url: '/users/login',
elementToWaitFor: 'submitButton',
elements: {
usernameInput: 'form[action="/users/login"] input[name="username"]',
passwordInput: 'form[action="/users/login"] input[name="password"]',
submitButton: 'form[action="/users/login"] [type=submit]'
},
enterUsername(value) {
// this.element('usernameInput').clear();
return this.element('usernameInput').sendKeys(value);
},
enterPassword(value) {
return this.element('passwordInput').sendKeys(value);
}
}),
account: Object.assign(page(driver), {
url: '/users/account',
elementToWaitFor: 'emailInput',
elements: {
emailInput: 'form[action="/users/account"] input[name="email"]'
}
})
});

57
test/e2e/tests/login.js Normal file
View file

@ -0,0 +1,57 @@
'use strict';
const config = require('../helpers/config');
const expect = require('chai').expect;
const driver = require('../helpers/driver');
const home = require('../page-objects/home')(driver);
const flash = require('../page-objects/flash')(driver);
const {
login,
account
} = require('../page-objects/users')(driver);
describe('login', function() {
this.timeout(10000);
before(() => driver.manage().deleteAllCookies());
it('can access home page', async () => {
await home.navigate();
});
it('can not access restricted content', async () => {
driver.navigate().to(config.baseUrl + '/settings');
flash.waitUntilVisible();
expect(await flash.getText()).to.contain('Need to be logged in to access restricted content');
await flash.clear();
});
it('can not login with false credentials', async () => {
login.enterUsername(config.users.admin.username);
login.enterPassword('invalid');
login.submit();
flash.waitUntilVisible();
expect(await flash.getText()).to.contain('Incorrect username or password');
await flash.clear();
});
it('can login as admin', async () => {
login.enterUsername(config.users.admin.username);
login.enterPassword(config.users.admin.password);
login.submit();
flash.waitUntilVisible();
expect(await flash.getText()).to.contain('Logged in as admin');
});
it('can access account page as admin', async () => {
await account.navigate();
});
it('can logout', async () => {
driver.navigate().to(config.baseUrl + '/users/logout');
flash.waitUntilVisible();
expect(await flash.getText()).to.contain('logged out');
});
after(() => driver.quit());
});

View file

@ -0,0 +1,101 @@
'use strict';
const config = require('../helpers/config');
const shortid = require('shortid');
const expect = require('chai').expect;
const driver = require('../helpers/driver');
const page = require('../page-objects/page')(driver);
const flash = require('../page-objects/flash')(driver);
const {
webSubscribe,
webConfirmSubscriptionNotice,
mailConfirmSubscription,
webSubscribedNotice,
mailSubscriptionConfirmed,
webUnsubscribe,
webUnsubscribedNotice,
mailUnsubscriptionConfirmed
} = require('../page-objects/subscription')(driver, config.lists.one);
const testuser = {
email: 'keep.' + shortid.generate() + '@mailtrain.org'
};
// console.log(testuser.email);
describe('subscribe (list one)', function() {
this.timeout(10000);
before(() => driver.manage().deleteAllCookies());
it('visits web-subscribe', async () => {
await webSubscribe.navigate();
});
it('submits invalid email (error)', async () => {
webSubscribe.enterEmail('foo@bar.nope');
webSubscribe.submit();
flash.waitUntilVisible();
expect(await flash.getText()).to.contain('Invalid email address');
});
it('submits valid email', async () => {
webSubscribe.enterEmail(testuser.email);
await webSubscribe.submit();
});
it('sees web-confirm-subscription-notice', async () => {
webConfirmSubscriptionNotice.waitUntilVisible();
expect(await page.containsText('Almost Finished')).to.be.true;
});
it('receives mail-confirm-subscription', async () => {
mailConfirmSubscription.navigate(testuser.email);
expect(await page.containsText('Please Confirm Subscription')).to.be.true;
});
it('clicks confirm subscription', async () => {
await mailConfirmSubscription.click('confirmLink');
});
it('sees web-subscribed-notice', async () => {
webSubscribedNotice.waitUntilVisible();
expect(await page.containsText('Subscription Confirmed')).to.be.true;
});
it('receives mail-subscription-confirmed', async () => {
mailSubscriptionConfirmed.navigate(testuser.email);
expect(await page.containsText('Subscription Confirmed')).to.be.true;
});
});
describe('unsubscribe (list one)', function() {
this.timeout(10000);
it('clicks unsubscribe', async () => {
await mailSubscriptionConfirmed.click('unsubscribeLink');
});
it('sees web-unsubscribe', async () => {
webUnsubscribe.waitUntilVisible();
expect(await page.containsText('Unsubscribe')).to.be.true;
});
it('clicks confirm unsubscription', async () => {
await webUnsubscribe.submit();
});
it('sees web-unsubscribed-notice', async () => {
webUnsubscribedNotice.waitUntilVisible();
expect(await page.containsText('Unsubscribe Successful')).to.be.true;
});
it('receives mail-unsubscription-confirmed', async () => {
mailUnsubscriptionConfirmed.navigate(testuser.email);
expect(await page.containsText('You Are Now Unsubscribed')).to.be.true;
});
after(() => driver.quit());
});

View file

@ -35,7 +35,7 @@
</head>
<body class="{{#if user}}logged-in user-{{user.username}}{{/if}}">
<body class="{{bodyClass}}">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">