New project structure

Beta of extract.js for extracting english locale
This commit is contained in:
Tomas Bures 2018-11-18 15:38:52 +01:00
parent e18d2b2f84
commit 2edbd67205
247 changed files with 6405 additions and 4237 deletions

13
server/test/e2e/.eslintrc Normal file
View file

@ -0,0 +1,13 @@
{
"parser": "babel-eslint",
"rules": {
"strict": 0,
"no-console": 0,
"comma-dangle": 0,
"arrow-body-style": 0,
"no-await-in-loop": 0
},
"env": {
"mocha": true
}
}

44
server/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`

28
server/test/e2e/index.js Normal file
View file

@ -0,0 +1,28 @@
'use strict';
require('./lib/exit-unless-test');
const mocha = require('./lib/mocha-e2e').mocha;
const path = require('path');
const only = 'only';
const skip = 'skip';
let tests = [
//'login',
'subscription'
];
tests = tests.map(testSpec => (testSpec.constructor === Array ? testSpec : [testSpec]));
tests = tests.filter(testSpec => testSpec[1] !== skip);
if (tests.some(testSpec => testSpec[1] === only)) {
tests = tests.filter(testSpec => testSpec[1] === only);
}
for (const testSpec of tests) {
const testPath = path.join(__dirname, 'tests', testSpec[0] + '.js');
mocha.addFile(testPath);
}
mocha.run(failures => {
process.exit(failures); // exit with non-zero status if there were failures
});

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,83 @@
'use strict';
const config = require('server/test/e2e/lib/config');
module.exports = {
app: config,
baseUrl: 'http://localhost:' + config.www.publicPort,
mailUrl: 'http://localhost:' + config.testServer.mailboxServerPort,
users: {
admin: {
username: 'admin',
password: 'test',
email: 'keep.admin@mailtrain.org',
accessToken: '7833d148e22c85474c314f43ae4591a7c9adec26'
}
},
lists: {
l1: {
id: 1,
cid: 'Hkj1vCoJb',
publicSubscribe: 1,
unsubscriptionMode: 0, // (one-step, no form)
customFields: [
{ type: 'text', key: 'MERGE_TEXT', column: 'custom_text_field_byiiqjrw' },
{ type: 'number', key: 'MERGE_NUMBER', column: 'custom_number_field_r1dd91awb' },
{ type: 'website', key: 'MERGE_WEBSITE', column: 'custom_website_field_rkq991cw' },
{ type: 'gpg', key: 'MERGE_GPG_PUBLIC_KEY', column: 'custom_gpg_public_key_ryvj51cz' },
{ type: 'longtext', key: 'MERGE_MULTILINE_TEXT', column: 'custom_multiline_text_bjbfojawb' },
{ type: 'json', key: 'MERGE_JSON', column: 'custom_json_skqjkcb' },
{ type: 'date-us', key: 'MERGE_DATE_MMDDYYYY', column: 'custom_date_mmddyy_rjkeojrzz' },
{ type: 'date-eur', key: 'MERGE_DATE_DDMMYYYY', column: 'custom_date_ddmmyy_ryedsk0wz' },
{ type: 'birthday-us', key: 'MERGE_BIRTHDAY_MMDD', column: 'custom_birthday_mmdd_h18coj0zz' },
{ type: 'birthday-eur', key: 'MERGE_BIRTHDAY_DDMM', column: 'custom_birthday_ddmm_r1g3s1czz' },
// TODO: Add remaining custom fields, dropdowns and checkboxes
]
},
l2: {
id: 2,
cid: 'SktV4HDZ-',
publicSubscribe: 1,
unsubscriptionMode: 1, // (one-step, with form)
customFields: []
},
l3: {
id: 3,
cid: 'BkdvNBw-W',
publicSubscribe: 1,
unsubscriptionMode: 2, // (two-step, no form)
customFields: []
},
l4: {
id: 4,
cid: 'rJMKVrDZ-',
publicSubscribe: 1,
unsubscriptionMode: 3, // (two-step, with form)
customFields: []
},
l5: {
id: 5,
cid: 'SJgoNSw-W',
publicSubscribe: 1,
unsubscriptionMode: 4, // (manual unsubscribe)
customFields: []
},
l6: {
id: 6,
cid: 'HyveEPvWW',
publicSubscribe: 0,
unsubscriptionMode: 0, // (one-step, no form)
customFields: []
}
},
settings: {
'service-url': 'http://localhost:' + config.www.publicPort + '/',
'admin-email': 'keep.admin@mailtrain.org',
'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,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);
}

View file

@ -0,0 +1,19 @@
'use strict';
const config = require('./config');
const driver = require('./mocha-e2e').driver;
const page = require('./page');
module.exports = (...extras) => page({
async fetchMail(address) {
await driver.sleep(1000);
await driver.navigate().to(`${config.mailUrl}/${address}`);
await this.waitUntilVisible();
},
async ensureUrl() {
throw new Error('Unsupported method.');
}
}, ...extras);

View file

@ -0,0 +1,213 @@
'use strict';
/* eslint-disable no-console */
const Mocha = require('mocha');
const color = Mocha.reporters.Base.color;
const WorkerCounter = require('./worker-counter');
const fs = require('fs-extra');
const config = require('./config');
const webdriver = require('selenium-webdriver');
const driver = new webdriver.Builder()
.forBrowser(config.app.seleniumWebDriver.browser || 'phantomjs')
.build();
const failHandlerRunning = new WorkerCounter();
function UseCaseReporter(runner) {
Mocha.reporters.Base.call(this, runner);
const self = this;
let indents = 0;
function indent () {
return Array(indents).join(' ');
}
runner.on('start', () => {
console.log();
});
runner.on('suite', suite => {
++indents;
console.log(color('suite', '%s%s'), indent(), suite.title);
});
runner.on('suite end', () => {
--indents;
if (indents === 1) {
console.log();
}
});
runner.on('use-case', useCase => {
++indents;
console.log();
console.log(color('suite', '%sUse case: %s'), indent(), useCase.title);
});
runner.on('use-case end', () => {
--indents;
});
runner.on('steps', useCase => {
++indents;
console.log(color('pass', '%s%s'), indent(), useCase.title);
});
runner.on('steps end', () => {
--indents;
});
runner.on('step pass', step => {
console.log(indent() + color('checkmark', ' ' + Mocha.reporters.Base.symbols.ok) + color('pass', ' %s'), step.title);
});
runner.on('step fail', step => {
console.log(indent() + color('fail', ' %s'), step.title);
});
runner.on('pending', test => {
const fmt = indent() + color('pending', ' - %s');
console.log(fmt, test.title);
});
runner.on('pass', test => {
let fmt;
if (test.speed === 'fast') {
fmt = indent() +
color('checkmark', ' ' + Mocha.reporters.Base.symbols.ok) +
color('pass', ' %s');
console.log(fmt, test.title);
} else {
fmt = indent() +
color('checkmark', ' ' + Mocha.reporters.Base.symbols.ok) +
color('pass', ' %s') +
color(test.speed, ' (%dms)');
console.log(fmt, test.title, test.duration);
}
});
runner.on('fail', (test, err) => {
failHandlerRunning.enter();
(async () => {
const currentUrl = await driver.getCurrentUrl();
const info = `URL: ${currentUrl}`;
await fs.writeFile('last-failed-e2e-test.info', info);
await fs.writeFile('last-failed-e2e-test.html', await driver.getPageSource());
await fs.writeFile('last-failed-e2e-test.png', Buffer.from(await driver.takeScreenshot(), 'base64'));
failHandlerRunning.exit();
})();
console.log(indent() + color('fail', ' %s'), test.title);
console.log();
console.log(err);
console.log();
console.log('Snaphot of and info about the current page are in last-failed-e2e-test.*');
});
runner.on('end', () => {
const stats = self.stats;
let fmt;
console.log();
// passes
fmt = color('bright pass', ' ') + color('green', ' %d passing');
console.log(fmt, stats.passes);
// pending
if (stats.pending) {
fmt = color('pending', ' ') + color('pending', ' %d pending');
console.log(fmt, stats.pending);
}
// failures
if (stats.failures) {
fmt = color('fail', ' %d failing');
console.log(fmt, stats.failures);
}
console.log();
});
}
const mocha = new Mocha()
.timeout(120000)
.reporter(UseCaseReporter)
.ui('tdd');
mocha._originalRun = mocha.run;
let runner;
mocha.run = fn => {
runner = mocha._originalRun(async () => {
await failHandlerRunning.waitForEmpty();
await driver.quit();
fn();
});
};
async function useCaseExec(name, asyncFn) {
runner.emit('use-case', {title: name});
try {
await asyncFn();
runner.emit('use-case end');
} catch (err) {
runner.emit('use-case end');
throw err;
}
}
function useCase(name, asyncFn) {
if (asyncFn) {
return test('Use case: ' + name, () => useCaseExec(name, asyncFn));
} else {
// Pending test
return test('Use case: ' + name);
}
}
useCase.only = (name, asyncFn) => test.only('Use case: ' + name, () => useCaseExec(name, asyncFn));
useCase.skip = (name, asyncFn) => test.skip('Use case: ' + name, () => useCaseExec(name, asyncFn));
async function step(name, asyncFn) {
try {
await asyncFn();
runner.emit('step pass', {title: name});
} catch (err) {
runner.emit('step fail', {title: name});
throw err;
}
}
async function steps(name, asyncFn) {
try {
runner.emit('steps', {title: name});
await asyncFn();
runner.emit('steps end');
} catch (err) {
runner.emit('step end');
throw err;
}
}
async function precondition(preConditionName, useCaseName, asyncFn) {
await steps(`Including use case "${useCaseName}" to satisfy precondition "${preConditionName}"`, asyncFn);
}
module.exports = {
mocha,
useCase,
step,
steps,
precondition,
driver
};

123
server/test/e2e/lib/page.js Normal file
View file

@ -0,0 +1,123 @@
'use strict';
const webdriver = require('selenium-webdriver');
const By = webdriver.By;
const until = webdriver.until;
const fs = require('fs-extra');
const driver = require('./mocha-e2e').driver;
const url = require('url');
const UrlPattern = require('url-pattern');
const waitTimeout = 10000;
module.exports = (...extras) => Object.assign({
elements: {},
async getElement(key) {
return await driver.findElement(By.css(this.elements[key] || key));
},
async getLinkParams(key) {
const elem = await driver.findElement(By.css(this.elements[key]));
const linkUrl = await elem.getAttribute('href');
const linkPath = url.parse(linkUrl).path;
const urlPattern = new UrlPattern(this.links[key]);
const params = urlPattern.match(linkPath);
if (!params) {
throw new Error(`Cannot match URL pattern ${this.links[key]}`);
}
return params;
},
async waitUntilVisible(selector) {
await driver.wait(until.elementLocated(By.css('body')), waitTimeout);
if (selector) {
await driver.wait(until.elementLocated(By.css(selector)), waitTimeout);
}
for (const elem of (this.elementsToWaitFor || [])) {
const sel = this.elements[elem];
if (!sel) {
throw new Error(`Element "${elem}" not found.`);
}
await driver.wait(until.elementLocated(By.css(sel)), waitTimeout);
}
for (const text of (this.textsToWaitFor || [])) {
await driver.wait(new webdriver.Condition(`for text "${text}"`, async () => await this.containsText(text)), waitTimeout);
}
if (this.url) {
await this.ensureUrl();
}
await driver.executeScript('document.mailTrainRefreshAcknowledged = true;');
},
async waitUntilVisibleAfterRefresh(selector) {
await driver.wait(new webdriver.Condition('for refresh', async driver => {
const val = await driver.executeScript('return document.mailTrainRefreshAcknowledged;');
return !val;
}), waitTimeout);
await this.waitUntilVisible(selector);
},
async click(key) {
const elem = await this.getElement(key);
await elem.click();
},
async getHref(key) {
const elem = await this.getElement(key);
return await elem.getAttribute('href');
},
async getText(key) {
const elem = await this.getElement(key);
return await elem.getText();
},
async getValue(key) {
const elem = await this.getElement(key);
return await elem.getAttribute('value');
},
async containsText(str) {
return await driver.executeScript(`
return (document.documentElement.innerText || document.documentElement.textContent).indexOf('${str}') > -1;
`);
},
async getSource() {
return await driver.getPageSource();
},
async saveSource(destPath) {
const src = await this.getSource();
await fs.writeFile(destPath, src);
},
async saveScreenshot(destPath) {
const pngData = await driver.takeScreenshot();
const buf = Buffer.from(pngData, 'base64');
await fs.writeFile(destPath, buf);
},
async saveSnapshot(destPathBase) {
destPathBase = destPathBase || 'last-failed-e2e-test';
const currentUrl = await driver.getCurrentUrl();
const info = `URL: ${currentUrl}`;
await fs.writeFile(destPathBase + '.info', info);
await this.saveSource(destPathBase + '.html');
await this.saveScreenshot(destPathBase + '.png');
},
async sleep(ms) {
await driver.sleep(ms);
}
}, ...extras);

View file

@ -0,0 +1,77 @@
'use strict';
const config = require('./config');
const By = require('selenium-webdriver').By;
const url = require('url');
const UrlPattern = require('url-pattern');
const driver = require('./mocha-e2e').driver;
const page = require('./page');
module.exports = (...extras) => page({
async navigate(pathOrParams) {
let path;
if (typeof pathOrParams === 'string') {
path = pathOrParams;
} else {
const urlPattern = new UrlPattern(this.requestUrl || this.url);
path = urlPattern.stringify(pathOrParams);
}
const parsedUrl = url.parse(path);
let absolutePath;
if (parsedUrl.host) {
absolutePath = path;
} else {
absolutePath = config.baseUrl + path;
}
await driver.navigate().to(absolutePath);
await this.waitUntilVisible();
},
async ensureUrl(path) {
const desiredUrl = path || this.url;
if (desiredUrl) {
const currentUrl = url.parse(await driver.getCurrentUrl());
const urlPattern = new UrlPattern(desiredUrl);
const params = urlPattern.match(currentUrl.pathname);
if (!params || config.baseUrl !== `${currentUrl.protocol}//${currentUrl.host}`) {
throw new Error(`Unexpected URL. Expecting ${config.baseUrl}${this.url} got ${currentUrl.protocol}//${currentUrl.host}/${currentUrl.pathname}`);
}
this.params = params;
}
},
async submit() {
const submitButton = await this.getElement('submitButton');
await submitButton.click();
},
async waitForFlash() {
await this.waitUntilVisible('div.alert:not(.js-warning)');
},
async getFlash() {
const elem = await driver.findElement(By.css('div.alert:not(.js-warning)'));
return await elem.getText();
},
async clearFlash() {
await driver.executeScript(`
var elements = document.getElementsByClassName('alert');
while(elements.length > 0){
elements[0].parentNode.removeChild(elements[0]);
}
`);
},
async setValue(key, value) {
const elem = await this.getElement(key);
await elem.clear();
await elem.sendKeys(value);
}
}, ...extras);

View file

@ -0,0 +1,33 @@
'use strict';
class WorkerCounter {
constructor() {
this.counter = 0;
}
enter() {
this.counter++;
}
exit() {
this.counter--;
}
async waitForEmpty() {
const self = this;
function wait(resolve) {
if (self.counter === 0) {
resolve();
} else {
setTimeout(wait, 500, resolve);
}
}
return new Promise(resolve => {
setTimeout(wait, 500, resolve);
});
}
}
module.exports = WorkerCounter;

View file

@ -0,0 +1,7 @@
'use strict';
const web = require('../lib/web');
module.exports = web({
url: '/'
});

View file

@ -0,0 +1,212 @@
'use strict';
const config = require('../lib/config');
const web = require('../lib/web');
const mail = require('../lib/mail');
const expect = require('chai').expect;
const fieldHelpers = list => ({
async fillFields(subscription) {
if (subscription.EMAIL && this.url === `/subscription/${list.cid}`) {
await this.setValue('emailInput', subscription.EMAIL);
}
if (subscription.FIRST_NAME) {
await this.setValue('firstNameInput', subscription.FIRST_NAME);
}
if (subscription.LAST_NAME) {
await this.setValue('lastNameInput', subscription.LAST_NAME);
}
for (const field of list.customFields) {
if (field.key in subscription) {
await this.setValue(`[name="${field.key}"]`, subscription[field.key]);
}
}
},
async assertFields(subscription) {
if (subscription.EMAIL) {
expect(await this.getValue('emailInput')).to.equal(subscription.EMAIL);
}
if (subscription.FIRST_NAME) {
expect(await this.getValue('firstNameInput')).to.equal(subscription.FIRST_NAME);
}
if (subscription.LAST_NAME) {
expect(await this.getValue('lastNameInput')).to.equal(subscription.LAST_NAME);
}
for (const field of list.customFields) {
if (field.key in subscription) {
expect(await this.getValue(`[name="${field.key}"]`)).to.equal(subscription[field.key]);
}
}
}
});
module.exports = list => ({
webSubscribe: web({
url: `/subscription/${list.cid}`,
elementsToWaitFor: ['form'],
textsToWaitFor: ['Subscribe to list'],
elements: {
form: `form[action="/subscription/${list.cid}/subscribe"]`,
emailInput: '#main-form input[name="EMAIL"]',
firstNameInput: '#main-form input[name="FIRST_NAME"]',
lastNameInput: '#main-form input[name="LAST_NAME"]',
submitButton: 'a[href="#submit"]'
}
}, fieldHelpers(list)),
webSubscribeAfterPost: web({
url: `/subscription/${list.cid}/subscribe`,
elementsToWaitFor: ['form'],
textsToWaitFor: ['Subscribe to list'],
elements: {
form: `form[action="/subscription/${list.cid}/subscribe"]`,
emailInput: '#main-form input[name="EMAIL"]',
firstNameInput: '#main-form input[name="FIRST_NAME"]',
lastNameInput: '#main-form input[name="LAST_NAME"]',
submitButton: 'a[href="#submit"]'
}
}, fieldHelpers(list)),
webSubscribeNonPublic: web({
url: `/subscription/${list.cid}`,
textsToWaitFor: ['Permission denied'],
}),
webConfirmSubscriptionNotice: web({
url: `/subscription/${list.cid}/confirm-subscription-notice`,
textsToWaitFor: ['We need to confirm your email address']
}),
mailConfirmSubscription: mail({
elementsToWaitFor: ['confirmLink'],
textsToWaitFor: ['Please Confirm Subscription'],
elements: {
confirmLink: `a[href^="${config.settings['service-url']}subscription/confirm/subscribe/"]`
}
}),
mailAlreadySubscribed: mail({
elementsToWaitFor: ['unsubscribeLink'],
textsToWaitFor: ['Email address already registered'],
elements: {
unsubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}/unsubscribe/"]`,
manageLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}/manage/"]`
},
links: {
unsubscribeLink: `/subscription/${list.cid}/unsubscribe/:ucid`,
manageLink: `/subscription/${list.cid}/manage/:ucid`
}
}),
webSubscribedNotice: web({
url: `/subscription/${list.cid}/subscribed-notice`,
textsToWaitFor: ['Subscription Confirmed']
}),
mailSubscriptionConfirmed: mail({
elementsToWaitFor: ['unsubscribeLink'],
textsToWaitFor: ['Subscription Confirmed'],
elements: {
unsubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}/unsubscribe/"]`,
manageLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}/manage/"]`
},
links: {
unsubscribeLink: `/subscription/${list.cid}/unsubscribe/:ucid`,
manageLink: `/subscription/${list.cid}/manage/:ucid`
}
}),
webManage: web({
url: `/subscription/${list.cid}/manage/:ucid`,
elementsToWaitFor: ['form'],
textsToWaitFor: ['Update Your Preferences'],
elements: {
form: `form[action="/subscription/${list.cid}/manage"]`,
emailInput: '#main-form input[name="EMAIL"]',
firstNameInput: '#main-form input[name="FIRST_NAME"]',
lastNameInput: '#main-form input[name="LAST_NAME"]',
submitButton: 'a[href="#submit"]',
manageAddressLink: `a[href^="/subscription/${list.cid}/manage-address/"]`
},
links: {
manageAddressLink: `/subscription/${list.cid}/manage-address/:ucid`
}
}, fieldHelpers(list)),
webManageAddress: web({
url: `/subscription/${list.cid}/manage-address/:ucid`,
elementsToWaitFor: ['form'],
textsToWaitFor: ['Update Your Email Address'],
elements: {
form: `form[action="/subscription/${list.cid}/manage-address"]`,
emailInput: '#main-form input[name="EMAIL"]',
emailNewInput: '#main-form input[name="EMAIL_NEW"]',
submitButton: 'a[href="#submit"]'
}
}),
mailConfirmAddressChange: mail({
elementsToWaitFor: ['confirmLink'],
textsToWaitFor: ['Please Confirm Subscription Address Change'],
elements: {
confirmLink: `a[href^="${config.settings['service-url']}subscription/confirm/change-address/"]`
}
}),
webUpdatedNotice: web({
url: `/subscription/${list.cid}/updated-notice`,
textsToWaitFor: ['Profile Updated']
}),
webUnsubscribedNotice: web({
url: `/subscription/${list.cid}/unsubscribed-notice`,
textsToWaitFor: ['Unsubscribe Successful']
}),
mailUnsubscriptionConfirmed: mail({
elementsToWaitFor: ['resubscribeLink'],
textsToWaitFor: ['You Are Now Unsubscribed'],
elements: {
resubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}"]`
}
}),
webUnsubscribe: web({
elementsToWaitFor: ['submitButton'],
textsToWaitFor: ['Unsubscribe'],
elements: {
submitButton: 'a[href="#submit"]'
}
}),
webConfirmUnsubscriptionNotice: web({
url: `/subscription/${list.cid}/confirm-unsubscription-notice`,
textsToWaitFor: ['We need to confirm your email address']
}),
mailConfirmUnsubscription: mail({
elementsToWaitFor: ['confirmLink'],
textsToWaitFor: ['Please Confirm Unsubscription'],
elements: {
confirmLink: `a[href^="${config.settings['service-url']}subscription/confirm/unsubscribe/"]`
}
}),
webManualUnsubscribeNotice: web({
url: `/subscription/${list.cid}/manual-unsubscribe-notice`,
elementsToWaitFor: ['contactLink'],
textsToWaitFor: ['Online Unsubscription Is Not Possible', config.settings['admin-email']],
elements: {
contactLink: `a[href^="mailto:${config.settings['admin-email']}"]`
}
}),
});

View file

@ -0,0 +1,29 @@
'use strict';
const web = require('../lib/web');
module.exports = {
login: web({
url: '/users/login',
elementsToWaitFor: ['submitButton'],
elements: {
usernameInput: 'form[action="/account/login"] input[name="username"]',
passwordInput: 'form[action="/account/login"] input[name="password"]',
submitButton: 'form[action="/account/login"] [type=submit]'
}
}),
logout: web({
requestUrl: '/users/logout',
url: '/'
}),
account: web({
url: '/users/account',
elementsToWaitFor: ['form'],
elements: {
form: 'form[action="/users/account"]',
emailInput: 'form[action="/users/account"] input[name="email"]'
}
})
};

View file

@ -0,0 +1,70 @@
'use strict';
/* eslint-disable prefer-arrow-callback */
const config = require('../lib/config');
const { useCase, step, driver } = require('../lib/mocha-e2e');
const expect = require('chai').expect;
const page = require('../page-objects/user');
const home = require('../page-objects/home');
suite('Login use-cases', () => {
before(() => driver.manage().deleteAllCookies());
test('User can access home page', async () => {
await home.navigate();
});
test('Anonymous user cannot access restricted content', async () => {
await driver.navigate().to(config.baseUrl + '/settings');
await page.login.waitUntilVisible();
await page.login.waitForFlash();
expect(await page.login.getFlash()).to.contain('Need to be logged in to access restricted content');
});
useCase('Login (invalid credential)', async () => {
await step('User navigates to the login page.', async () => {
await page.login.navigate();
});
await step('User fills in the user name and incorrect password.', async () => {
await page.login.setValue('usernameInput', config.users.admin.username);
await page.login.setValue('passwordInput', 'invalid');
await page.login.submit();
});
await step('System shows a flash notice that credentials are invalid.', async () => {
await page.login.waitForFlash();
expect(await page.login.getFlash()).to.contain('Incorrect username or password');
});
});
useCase('Login and logout', async () => {
await step('User navigates to the login page.', async () => {
await page.login.navigate();
});
await step('User fills in the user name and password.', async () => {
await page.login.setValue('usernameInput', config.users.admin.username);
await page.login.setValue('passwordInput', config.users.admin.password);
await page.login.submit();
});
await step('System shows the home page and a flash notice that user has been logged in.', async () => {
await home.waitUntilVisibleAfterRefresh();
await home.waitForFlash();
expect(await home.getFlash()).to.contain('Logged in as admin');
});
await step('User navigates to its account.', async () => {
await page.account.navigate();
});
await step('User logs out.', async () => {
await page.logout.navigate();
await home.waitForFlash();
expect(await home.getFlash()).to.contain('logged out');
});
});
});

View file

@ -0,0 +1,646 @@
'use strict';
/* eslint-disable prefer-arrow-callback */
const config = require('../lib/config');
const { useCase, step, precondition, driver } = require('../lib/mocha-e2e');
const shortid = require('shortid');
const expect = require('chai').expect;
const createPage = require('../page-objects/subscription');
const faker = require('faker');
const request = require('request-promise');
function getPage(listConf) {
return createPage(listConf);
}
function generateEmail() {
return 'keep.' + shortid.generate() + '@gmail.com';
}
function generateCustomFieldValue(field) {
// https://github.com/marak/Faker.js/#api-methods
switch (field.type) {
case 'text':
return faker.lorem.words();
case 'website':
return faker.internet.url();
case 'gpg':
return '';
case 'longtext':
return faker.lorem.lines();
case 'json':
return `{"say":"${faker.lorem.word()}"}`;
case 'number':
return faker.random.number().toString();
case 'option':
return Math.round(Math.random());
case 'date-us':
return '10/20/2017';
case 'date-eur':
return '20/10/2017';
case 'birthday-us':
return '10/20';
case 'birthday-eur':
return '20/10';
default:
return '';
}
}
function generateSubscriptionData(listConf) {
const data = {
EMAIL: generateEmail(),
FIRST_NAME: faker.name.firstName(),
LAST_NAME: faker.name.lastName(),
TIMEZONE: 'Europe/Tallinn',
};
listConf.customFields.forEach(field => {
data[field.key] = generateCustomFieldValue(field);
});
return data;
}
function changeSubscriptionData(listConf, subscription) {
const data = generateSubscriptionData(listConf);
delete data.EMAIL;
const changedSubscription = Object.assign({}, subscription, data);
return changedSubscription;
}
async function subscribe(listConf, subscription) {
const page = getPage(listConf);
await step('User navigates to list subscription page.', async () => {
await page.webSubscribe.navigate();
});
await step('User submits a valid email and other subscription info.', async () => {
await page.webSubscribe.fillFields(subscription);
await page.webSubscribe.submit();
});
await step('System shows a notice that further instructions are in the email.', async () => {
await page.webConfirmSubscriptionNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with a link to confirm the subscription.', async () => {
await page.mailConfirmSubscription.fetchMail(subscription.EMAIL);
});
await step('User clicks confirm subscription in the email', async () => {
await page.mailConfirmSubscription.click('confirmLink');
});
await step('System shows a notice that subscription has been confirmed.', async () => {
await page.webSubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with subscription confirmation.', async () => {
await page.mailSubscriptionConfirmed.fetchMail(subscription.EMAIL);
subscription.unsubscribeLink = await page.mailSubscriptionConfirmed.getHref('unsubscribeLink');
subscription.manageLink = await page.mailSubscriptionConfirmed.getHref('manageLink');
const unsubscribeParams = await page.mailSubscriptionConfirmed.getLinkParams('unsubscribeLink');
const manageParams = await page.mailSubscriptionConfirmed.getLinkParams('manageLink');
expect(unsubscribeParams.ucid).to.equal(manageParams.ucid);
subscription.ucid = unsubscribeParams.ucid;
});
return subscription;
}
async function subscriptionExistsPrecondition(listConf, subscription) {
await precondition('Subscription exists', 'Subscription to a public list (main scenario)', async () => {
await subscribe(listConf, subscription);
});
return subscription;
}
suite('Subscription use-cases', () => {
before(() => driver.manage().deleteAllCookies());
useCase('Subscription to a public list (main scenario)', async () => {
await subscribe(config.lists.l1, {
EMAIL: generateEmail()
});
});
useCase('Subscription to a public list (invalid email)', async () => {
const page = getPage(config.lists.l1);
await step('User navigates to list subscribe page', async () => {
await page.webSubscribe.navigate();
});
await step('User submits an invalid email.', async () => {
await page.webSubscribe.setValue('emailInput', 'foo@bar.nope');
await page.webSubscribe.submit();
});
await step('System shows a flash notice that email is invalid.', async () => {
await page.webSubscribeAfterPost.waitForFlash();
expect(await page.webSubscribeAfterPost.getFlash()).to.contain('Invalid email address');
});
});
useCase('Subscription to a public list (email already registered)', async () => {
const page = getPage(config.lists.l1);
const subscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail()
});
await step('User navigates to list subscribe page', async () => {
await page.webSubscribe.navigate();
});
await step('User submits the email which has been already registered.', async () => {
await page.webSubscribe.setValue('emailInput', subscription.EMAIL);
await page.webSubscribe.submit();
});
await step('System shows a notice that further instructions are in the email.', async () => {
await page.webConfirmSubscriptionNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email informing that the address has been already registered.', async () => {
await page.mailAlreadySubscribed.fetchMail(subscription.EMAIL);
});
});
useCase('Subscription to a non-public list', async () => {
const page = getPage(config.lists.l6);
await step('User navigates to list subscription page and sees message that this list does not allow public subscriptions.', async () => {
await page.webSubscribeNonPublic.navigate();
});
});
useCase('Change profile info', async () => {
const page = getPage(config.lists.l1);
let subscription = await subscriptionExistsPrecondition(config.lists.l1, generateSubscriptionData(config.lists.l1));
await step('User clicks the manage subscription button.', async () => {
await page.mailSubscriptionConfirmed.click('manageLink');
});
await step('Systems shows a form to change subscription details. The form contains data entered during subscription.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.assertFields(subscription);
});
await step('User enters other values and submits the form.', async () => {
subscription = changeSubscriptionData(config.lists.l1, subscription);
await page.webManage.fillFields(subscription);
await page.webManage.submit();
});
await step('Systems shows a notice that profile has been updated.', async () => {
await page.webUpdatedNotice.waitUntilVisibleAfterRefresh();
});
await step('User navigates to manage subscription again.', async () => {
await page.webManage.navigate({ ucid: subscription.ucid });
});
await step('Systems shows a form with the changes made previously.', async () => {
await page.webManage.assertFields(subscription);
});
});
useCase('Change email', async () => {
const page = getPage(config.lists.l1);
const subscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail(),
FIRST_NAME: 'John',
LAST_NAME: 'Doe'
});
await step('User clicks the manage subscription button.', async () => {
await page.mailSubscriptionConfirmed.click('manageLink');
});
await step('Systems shows a form to change subscription details. The form contains data entered during subscription.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.fillFields(subscription);
});
await step('User clicks the change address button.', async () => {
await page.webManage.click('manageAddressLink');
});
await step('Systems shows a form to change email.', async () => {
await page.webManageAddress.waitUntilVisibleAfterRefresh();
});
await step('User fills in a new email address and submits the form.', async () => {
subscription.EMAIL = generateEmail();
await page.webManageAddress.setValue('emailNewInput', subscription.EMAIL);
await page.webManageAddress.submit();
});
await step('System goes back to the profile form and shows a flash notice that further instructions are in the email.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.waitForFlash();
expect(await page.webManage.getFlash()).to.contain('An email with further instructions has been sent to the provided address');
});
await step('System sends an email with a link to confirm the address change.', async () => {
await page.mailConfirmAddressChange.fetchMail(subscription.EMAIL);
});
await step('User clicks confirm subscription in the email', async () => {
await page.mailConfirmAddressChange.click('confirmLink');
});
await step('System shows the profile form with a flash notice that address has been changed.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.waitForFlash();
expect(await page.webManage.getFlash()).to.contain('Email address changed');
expect(await page.webManage.getValue('emailInput')).to.equal(subscription.EMAIL);
});
await step('System sends an email with subscription confirmation.', async () => {
await page.mailSubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
});
useCase('Unsubscription from list #1 (one-step, no form).', async () => {
const page = getPage(config.lists.l1);
const subscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail()
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
});
useCase('Unsubscription from list #2 (one-step, with form).', async () => {
const page = getPage(config.lists.l2);
const subscription = await subscriptionExistsPrecondition(config.lists.l2, {
EMAIL: generateEmail()
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('Systems shows a form to unsubscribe.', async () => {
await page.webUnsubscribe.waitUntilVisibleAfterRefresh();
});
await step('User confirms unsubscribe and clicks the unsubscribe button.', async () => {
await page.webUnsubscribe.submit();
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
});
useCase('Unsubscription from list #3 (two-step, no form).', async () => {
const page = getPage(config.lists.l3);
const subscription = await subscriptionExistsPrecondition(config.lists.l3, {
EMAIL: generateEmail()
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('System shows a notice that further instructions are in the email.', async () => {
await page.webConfirmUnsubscriptionNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with a link to confirm unsubscription.', async () => {
await page.mailConfirmUnsubscription.fetchMail(subscription.EMAIL);
});
await step('User clicks the confirm unsubscribe button in the email.', async () => {
await page.mailConfirmUnsubscription.click('confirmLink');
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
});
useCase('Unsubscription from list #4 (two-step, with form).', async () => {
const page = getPage(config.lists.l4);
const subscription = await subscriptionExistsPrecondition(config.lists.l4, {
EMAIL: generateEmail()
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('Systems shows a form to unsubscribe.', async () => {
await page.webUnsubscribe.waitUntilVisibleAfterRefresh();
});
await step('User confirms unsubscribe and clicks the unsubscribe button.', async () => {
await page.webUnsubscribe.submit();
});
await step('System shows a notice that further instructions are in the email.', async () => {
await page.webConfirmUnsubscriptionNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with a link to confirm unsubscription.', async () => {
await page.mailConfirmUnsubscription.fetchMail(subscription.EMAIL);
});
await step('User clicks the confirm unsubscribe button in the email.', async () => {
await page.mailConfirmUnsubscription.click('confirmLink');
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
});
useCase('Unsubscription from list #5 (manual unsubscribe).', async () => {
const page = getPage(config.lists.l5);
await subscriptionExistsPrecondition(config.lists.l5, {
EMAIL: generateEmail()
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('Systems shows a notice that online unsubscription is not possible.', async () => {
await page.webManualUnsubscribeNotice.waitUntilVisibleAfterRefresh();
});
});
useCase('Resubscription.', async () => {
const page = getPage(config.lists.l1);
const subscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail(),
FIRST_NAME: 'John',
LAST_NAME: 'Doe'
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
await step('User clicks the resubscribe button.', async () => {
await page.mailUnsubscriptionConfirmed.click('resubscribeLink');
});
await step('Systems shows the subscription form. The form contains data entered during initial subscription.', async () => {
await page.webSubscribe.waitUntilVisibleAfterRefresh();
expect(await page.webSubscribe.getValue('emailInput')).to.equal(subscription.EMAIL);
expect(await page.webSubscribe.getValue('firstNameInput')).to.equal(subscription.FIRST_NAME);
expect(await page.webSubscribe.getValue('lastNameInput')).to.equal(subscription.LAST_NAME);
});
await step('User submits the subscription form.', async () => {
await page.webSubscribe.submit();
});
await step('System shows a notice that further instructions are in the email.', async () => {
await page.webConfirmSubscriptionNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with a link to confirm the subscription.', async () => {
await page.mailConfirmSubscription.fetchMail(subscription.EMAIL);
});
await step('User clicks confirm subscription in the email', async () => {
await page.mailConfirmSubscription.click('confirmLink');
});
await step('System shows a notice that subscription has been confirmed.', async () => {
await page.webSubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with subscription confirmation. The manage and unsubscribe links are identical with the initial subscription.', async () => {
await page.mailSubscriptionConfirmed.fetchMail(subscription.EMAIL);
const unsubscribeLink = await page.mailSubscriptionConfirmed.getHref('unsubscribeLink');
const manageLink = await page.mailSubscriptionConfirmed.getHref('manageLink');
expect(subscription.unsubscribeLink).to.equal(unsubscribeLink);
expect(subscription.manageLink).to.equal(manageLink);
});
});
useCase('A subscriber address can be changed to an address which has been previously unsubscribed. #222', async () => {
const page = getPage(config.lists.l1);
const oldSubscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail(),
FIRST_NAME: 'old first name',
LAST_NAME: 'old last name'
});
await step('User clicks the unsubscribe button.', async () => {
await page.mailSubscriptionConfirmed.click('unsubscribeLink');
});
await step('System shows a notice that confirms unsubscription.', async () => {
await page.webUnsubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email that confirms unsubscription.', async () => {
await page.mailUnsubscriptionConfirmed.fetchMail(oldSubscription.EMAIL);
});
const newSubscription = await subscriptionExistsPrecondition(config.lists.l1, {
EMAIL: generateEmail(),
FIRST_NAME: 'new first name'
});
await step('User clicks the manage subscription button.', async () => {
await page.mailSubscriptionConfirmed.click('manageLink');
});
await step('User clicks the change address button.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.click('manageAddressLink');
});
await step('Systems shows a form to change email.', async () => {
await page.webManageAddress.waitUntilVisibleAfterRefresh();
});
await step('User fills in the email address of the original subscription and submits the form.', async () => {
await page.webManageAddress.setValue('emailNewInput', oldSubscription.EMAIL);
await page.webManageAddress.submit();
});
await step('System goes back to the profile form and shows a flash notice that further instructions are in the email.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.waitForFlash();
expect(await page.webManage.getFlash()).to.contain('An email with further instructions has been sent to the provided address');
});
await step('System sends an email with a link to confirm the address change.', async () => {
await page.mailConfirmAddressChange.fetchMail(oldSubscription.EMAIL);
});
await step('User clicks confirm subscription in the email', async () => {
await page.mailConfirmAddressChange.click('confirmLink');
});
await step('System shows the profile form with a flash notice that address has been changed. The form does not contain data from the old subscription.', async () => {
await page.webManage.waitUntilVisibleAfterRefresh();
await page.webManage.waitForFlash();
expect(await page.webManage.getFlash()).to.contain('Email address changed');
expect(await page.webManage.getValue('emailInput')).to.equal(oldSubscription.EMAIL);
expect(await page.webManage.getValue('firstNameInput')).to.equal(newSubscription.FIRST_NAME);
expect(await page.webManage.getValue('lastNameInput')).to.equal('');
});
await step('System sends an email with subscription confirmation.', async () => {
await page.mailSubscriptionConfirmed.fetchMail(oldSubscription.EMAIL);
});
});
});
async function apiSubscribe(listConf, subscription) {
await step('Add subscription via API call.', async () => {
const response = await request({
uri: `${config.baseUrl}/api/subscribe/${listConf.cid}?access_token=${config.users.admin.accessToken}`,
method: 'POST',
json: subscription
});
expect(response.error).to.be.a('undefined');
expect(response.data.id).to.be.a('string');
subscription.ucid = response.data.id;
});
return subscription;
}
suite('API Subscription use-cases', () => {
useCase('Subscription to list #1, without confirmation.', async () => {
const page = getPage(config.lists.l1);
const subscription = await apiSubscribe(config.lists.l1, generateSubscriptionData(config.lists.l1));
await step('User navigates to manage subscription.', async () => {
await page.webManage.navigate({ ucid: subscription.ucid });
});
await step('Systems shows a form containing the data submitted with the API call.', async () => {
await page.webManage.assertFields(subscription);
});
});
useCase('Subscription to list #1, with confirmation.', async () => {
const page = getPage(config.lists.l1);
const subscription = await apiSubscribe(config.lists.l1, Object.assign(generateSubscriptionData(config.lists.l1), {
REQUIRE_CONFIRMATION: 'yes'
}));
await step('System sends an email with a link to confirm the subscription.', async () => {
await page.mailConfirmSubscription.fetchMail(subscription.EMAIL);
});
await step('User clicks confirm subscription in the email', async () => {
await page.mailConfirmSubscription.click('confirmLink');
});
await step('System shows a notice that subscription has been confirmed.', async () => {
await page.webSubscribedNotice.waitUntilVisibleAfterRefresh();
});
await step('System sends an email with subscription confirmation.', async () => {
await page.mailSubscriptionConfirmed.fetchMail(subscription.EMAIL);
});
await step('User clicks the manage subscription button.', async () => {
await page.mailSubscriptionConfirmed.click('manageLink');
});
await step('Systems shows a form containing the data submitted with the API call.', async () => {
await page.webManage.assertFields(subscription);
});
});
useCase('Change profile info', async () => {
const page = getPage(config.lists.l1);
const initialSubscription = await apiSubscribe(config.lists.l1, generateSubscriptionData(config.lists.l1));
const update = changeSubscriptionData(config.lists.l1, initialSubscription);
delete update.FIRST_NAME;
const changedSubscription = await apiSubscribe(config.lists.l1, update);
changedSubscription.FIRST_NAME = initialSubscription.FIRST_NAME;
expect(changedSubscription.ucid).to.equal(initialSubscription.ucid);
await step('User navigates to manage subscription.', async () => {
await page.webManage.navigate({ ucid: changedSubscription.ucid });
});
await step('Systems shows a form containing the updated subscription data.', async () => {
await page.webManage.assertFields(changedSubscription);
});
});
useCase('Unsubscribe', async () => {
const subscription = await apiSubscribe(config.lists.l1, generateSubscriptionData(config.lists.l1));
await step('Unsubsribe via API call.', async () => {
const response = await request({
uri: `${config.baseUrl}/api/unsubscribe/${config.lists.l1.cid}?access_token=${config.users.admin.accessToken}`,
method: 'POST',
json: {
EMAIL: subscription.EMAIL
}
});
expect(response.error).to.be.a('undefined');
expect(response.data.id).to.be.a('number'); // FIXME Shouldn't data.id be the cid instead of the DB id?
expect(response.data.unsubscribed).to.equal(true);
});
});
});