diff --git a/LICENSE b/LICENSE index 597d8fa73..e0a687532 100644 --- a/LICENSE +++ b/LICENSE @@ -4,8 +4,12 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. -See the License for the specific language governing permissions and limitations under the License. +See the License for the specific language governing permissions +and limitations under the License. diff --git a/README.md b/README.md index a089f040c..89474bf6d 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,37 @@ -# Talk +# Talk · [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) · [![NSP Status](https://nodesecurity.io/orgs/coralproject/projects/07ce2e4c-99fb-48f8-b50b-69d2d2c081b8/badge)](https://nodesecurity.io/orgs/coralproject/projects/07ce2e4c-99fb-48f8-b50b-69d2d2c081b8) · [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md#pull-requests) -[![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fcoralproject%2Ftalk&env[TALK_FACEBOOK_APP_ID]=ignore&env[TALK_FACEBOOK_APP_SECRET]=ignore) -[![NSP Status](https://nodesecurity.io/orgs/coralproject/projects/07ce2e4c-99fb-48f8-b50b-69d2d2c081b8/badge)](https://nodesecurity.io/orgs/coralproject/projects/07ce2e4c-99fb-48f8-b50b-69d2d2c081b8) - -Online comments are broken. Our open-source Talk tool rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/products/talk.html). +Online comments are broken. Our open-source commenting platform, Talk, rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/products/talk.html). Built with <3 by The Coral Project & Mozilla. -## Getting Started +## Try Talk! -Check out our Docs: https://coralproject.github.io/talk/ +You're just one click away from trying Talk - all you need is a Heroku account and a few minutes of your time. + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fcoralproject%2Ftalk&env[TALK_FACEBOOK_APP_ID]=ignore&env[TALK_FACEBOOK_APP_SECRET]=ignore) + +## Technical Documentation + +From getting up and running, to advanced configuration, to how to scale Talk, our [Talk Technical Docs](https://coralproject.github.io/talk/) have everything you need to know. + +## Product Guide + +Learn more about Talk, including a deep dive into features for commenters and moderators, and FAQs in our [Talk Product Guide](https://coralproject.github.io/talk/how-talk-works). ## Relevant Links -- Blog: https://blog.coralproject.net/ -- Community Forums: https://community.coralproject.net/ -- Community Guides for Journalism: https://guides.coralproject.net/ -- Project: https://coralproject.net/ -- Roadmap: https://www.pivotaltracker.com/n/projects/1863625 +- [Our Blog](https://blog.coralproject.net/) +- [Community Forums](https://community.coralproject.net/) +- [Community Guides for Journalism](https://guides.coralproject.net/) +- [More About Us](https://coralproject.net/) +- [Talk Roadmap](https://www.pivotaltracker.com/n/projects/1863625) ## End-to-End Testing -Talk uses [Nightwatch](http://nightwatchjs.org/) to write e2e tests. The testing infrastructure that allows us to run our tests in real browsers is provided with love by our friends at Browserstack. +Talk uses [Nightwatch](https://nightwatchjs.org/) as our e2e testing framework. The testing infrastructure that allows us to run our tests in real browsers is provided with love by our friends at [Browserstack](https://browserstack.com). - -![](/public/img/browserstack_logo.png) +[![Browserstack](/public/img/browserstack_logo.png)](https://browserstack.com) ## License - Copyright 2017 Mozilla Foundation - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - either express or implied. - - See the License for the specific language governing permissions - and limitations under the License. +Talk is released under the [Apache License, v2.0](/LICENSE). diff --git a/circle.yml b/circle.yml index 932643ff6..9f35736c6 100644 --- a/circle.yml +++ b/circle.yml @@ -50,7 +50,7 @@ test: - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test # Check dependancies using nsp. - nsp check - # - yarn e2e-ci + - yarn e2e-ci deployment: release: diff --git a/nightwatch-browserstack.conf.js b/nightwatch-browserstack.conf.js index 9e8e334c5..d79f6917a 100644 --- a/nightwatch-browserstack.conf.js +++ b/nightwatch-browserstack.conf.js @@ -26,18 +26,29 @@ const nightwatch_config = { // Disable this, as it makes bs slow and brittle. 'browserstack.networkLogs': false, - } + 'browserstack.resolution': '1600x1200', + }, + screenshots : { + enabled: true, + on_failure: true, + on_error: true, + path: process.env.REPORTS_FOLDER || './test/e2e/reports', + }, }, chrome: { desiredCapabilities: { browser: 'chrome', browser_version: '62', + os: 'Windows', + os_version: '10', }, }, firefox: { desiredCapabilities: { browser: 'firefox', browser_version: '56', + os: 'Windows', + os_version: '10', }, }, safari: { @@ -52,14 +63,21 @@ const nightwatch_config = { desiredCapabilities: { browser: 'internet explorer', os: 'Windows', - os_version: '8.1', + os_version: '10', browser_version: '11', + + // The x64 bit IEDriver that is used by IE 11 has a known issue with sendKeys where + // it may enter incorrect keys (shift + key). + // This adds a delay for each character as temporary fix as advised from the browserstack support. + 'browserstack.customSendKeys': 800, }, }, edge: { desiredCapabilities: { browser: 'edge', browser_version: '15', + os: 'Windows', + os_version: '10', }, }, } diff --git a/nightwatch.conf.js b/nightwatch.conf.js index f843b881a..97eee0e81 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -9,13 +9,13 @@ module.exports = { globals_path: './test/e2e/globals', selenium: { start_process: true, - server_path: 'node_modules/selenium-standalone/.selenium/selenium-server/3.5.3-server.jar', + server_path: 'node_modules/selenium-standalone/.selenium/selenium-server/3.6.0-server.jar', log_path: './test/e2e/', host: '127.0.0.1', port: 6666, cli_args: { - 'webdriver.chrome.driver': 'node_modules/selenium-standalone/.selenium/chromedriver/2.32-x64-chromedriver', - 'webdriver.gecko.driver': 'node_modules/selenium-standalone/.selenium/geckodriver/0.18.0-x64-geckodriver', + 'webdriver.chrome.driver': 'node_modules/selenium-standalone/.selenium/chromedriver/2.33-x64-chromedriver', + 'webdriver.gecko.driver': 'node_modules/selenium-standalone/.selenium/geckodriver/0.19.0-x64-geckodriver', } }, test_settings: { @@ -43,7 +43,7 @@ module.exports = { 'chrome-headless': { desiredCapabilities: { chromeOptions : { - args: ['--headless', '--disable-gpu', 'window-size=1280,800'], + args: ['--headless', '--disable-gpu', 'window-size=1600,1200'], }, }, }, diff --git a/package.json b/package.json index 3e9d5fb05..897efc819 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "redux": "^3.6.0", "redux-thunk": "^2.1.0", "resolve": "^1.4.0", - "selenium-standalone": "6.9.0", + "selenium-standalone": "^6.11.0", "semver": "^5.4.1", "simplemde": "^1.11.2", "smoothscroll-polyfill": "^0.3.5", diff --git a/scripts/e2e-ci.sh b/scripts/e2e-ci.sh index e0e94b4bf..e72d1a6ce 100755 --- a/scripts/e2e-ci.sh +++ b/scripts/e2e-ci.sh @@ -1,32 +1,87 @@ #!/bin/bash +CIRCLE_TEST_REPORTS=${CIRCLE_TEST_REPORTS:-./test/e2e/reports} +CIRCLE_BRANCH=${CIRCLE_BRANCH:-master} + +# Amount of retries before failure. +E2E_MAX_RETRIES=${E2E_MAX_RETRIES:-1} + +# Amount of seconds between tests. +E2E_SLEEP_BETWEEN_TESTS=${E2E_SLEEP_BETWEEN_TESTS:-1} + +# Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old. +BROWSERS="chrome firefox ie edge" #safari + if [[ "${CIRCLE_BRANCH}" == "master" ]]; then + # List of failed browsers. + failedBrowsers= + + # List of succeeded browsers. + succeededBrowsers= + exitCode=0 browserstack() { - REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/$1" yarn e2e-browserstack -- --env "$1" + + # Current number of tries. + try=${2:-0} + + echo "-- Start e2e for $1 #$try --" + + REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/$1" yarn e2e-browserstack --env "$1" # Determine exit code. result=$? - if [ "$result" -gt "0" ] - then + if [ "$result" -ne "0" ]; then + echo "-- Failed e2e for $1 #$try --" + + # Try again until E2E_MAX_RETRIES is reached. + if [ "$try" -lt "$E2E_MAX_RETRIES" ]; then + let try=try+1 + + # Sleep a bit to let browserstack-local close properly. + sleep "$E2E_SLEEP_BETWEEN_TESTS" + + browserstack "$1" "$try" + return + fi + + # Failed, add to list of failed browsers. + failedBrowsers="$failedBrowsers $1" + + # Remember exit code. exitCode=$result + else + echo "-- Success e2e for $1 #$try --" + + # Succeeded, add to list of succeeded browsers. + succeededBrowsers="$succeededBrowsers $1" + eval "browser_${1}_succeeded_at=$try" fi - # Sleep a bit to let browserstack-local to close properly. - sleep 2 + # Sleep a bit to let browserstack-local close properly. + sleep "$E2E_SLEEP_BETWEEN_TESTS" } # Test using browserstack. - browserstack chrome - browserstack firefox - browserstack ie - browserstack edge + for browser in $BROWSERS + do + browserstack "$browser" + done - # Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old. - # browserstack safari + # Print information about succeeded browsers. + for x in $succeededBrowsers + do + echo "Succeeded $x at try #$(eval "echo \$browser_${x}_succeeded_at")" + done + + # Print information about failed browsers. + for x in $failedBrowsers + do + echo "Failed $x" + done exit $exitCode else # When browserstack is not available test locally using chrome headless. diff --git a/services/email/banned.html.ejs b/services/email/banned.html.ejs new file mode 100644 index 000000000..4fd8f191c --- /dev/null +++ b/services/email/banned.html.ejs @@ -0,0 +1 @@ +<%= body.replace(/\n/g, '
') %> diff --git a/services/email/banned.txt.ejs b/services/email/banned.txt.ejs new file mode 100644 index 000000000..b36560ec5 --- /dev/null +++ b/services/email/banned.txt.ejs @@ -0,0 +1 @@ +<%= body %> diff --git a/services/users.js b/services/users.js index 9906ef6c0..e0f7d1599 100644 --- a/services/users.js +++ b/services/users.js @@ -398,7 +398,7 @@ module.exports = class UsersService { // TODO: current updating status behavior is weird. // once a user has been `APPROVED` its status cannot be // changed anymore. - return UserModel.findOneAndUpdate({ + const user = await UserModel.findOneAndUpdate({ id, status: { $ne: 'APPROVED' @@ -410,6 +410,25 @@ module.exports = class UsersService { }, { new: true, }); + + if (status === 'BANNED') { + let localProfile = user.profiles.find((profile) => profile.provider === 'local'); + if (localProfile) { + const options = + { + template: 'banned', // needed to know which template to render! + locals: { // specifies the template locals. + body: 'In accordance with The Coral Project’s community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community.' + }, + subject: 'Your account has been banned', + to: localProfile.id // This only works if the user has registered via e-mail. + // We may want a standard way to access a user's e-mail address in the future + }; + await MailerService.sendSimple(options); + } + + } + return user; } /** diff --git a/test/e2e/page_objects/embedStream.js b/test/e2e/page_objects/embedStream.js index 76065ba5e..f148ae7bc 100644 --- a/test/e2e/page_objects/embedStream.js +++ b/test/e2e/page_objects/embedStream.js @@ -8,6 +8,11 @@ module.exports = { }, getEmbedSection: function() { this.waitForElementVisible('@iframe'); + + // Pause a bit to let iframe initialize in the hope that it'll + // fix https://www.browserstack.com/automate/builds/96419cf46e3d6376a36ae6d3f90934112df1ed91/sessions/224f1a1566c1c8c7859e2e76ece51862200f0173#automate_button + this.api.pause(200); + this.api.frame(iframeId); this.expect.section('@embed').to.be.present; return this.section.embed; @@ -23,11 +28,13 @@ module.exports = { embed: { commands: [{ getProfileSection: function() { + this.waitForElementVisible('@profileTabButton'); this.click('@profileTabButton'); this.expect.section('@profile').to.be.present; return this.section.profile; }, getCommentsSection: function() { + this.waitForElementVisible('@commentsTabButton'); this.click('@commentsTabButton'); this.expect.section('@comments').to.be.present; return this.section.comments; diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 4a60780bb..4744d2116 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -1,9 +1,9 @@ module.exports = { '@tags': ['admin', 'login'], beforeEach: (client) => { - + client.resizeWindow(1024, 800); - }, + }, 'Admin logs in': (client) => { const adminPage = client.page.admin(); const {testData: {admin}} = client.globals; @@ -17,8 +17,6 @@ module.exports = { .waitForElementVisible('@signInButton') .click('@signInButton'); - client.pause(3000); - adminPage .waitForElementVisible('@moderationContainer'); }, diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index 57f9a8fd2..53da1e87d 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -1,3 +1,5 @@ +const SortedWindowHandler = require('../utils/SortedWindowHandler'); + module.exports = { '@tags': ['embedStream', 'login'], 'creates a new asset': (client) => { @@ -18,16 +20,19 @@ module.exports = { .navigate() .getEmbedSection(); + const windowHandler = new SortedWindowHandler(client); + embed .waitForElementVisible('@signInButton') .click('@signInButton'); - client.pause(3000); + // Wait for window to be created + // https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/17b1a79682bef2498cb0be86eac317a08c976b0a#automate_button + client.pause(200); // Focusing on the Login PopUp - client.windowHandles((result) => { - const handle = result.value[1]; - client.switchWindow(handle); + windowHandler.windowHandles((handles) => { + client.switchWindow(handles[1]); }); const login = client.page.login(); @@ -45,10 +50,19 @@ module.exports = { .waitForElementVisible('@loginButton') .click('@loginButton'); + // Give a tiny bit of time to let popup close. + client.pause(50); + + if (client.capabilities.browserName === 'MicrosoftEdge') { + + // More time for edge. + // https://www.browserstack.com/automate/builds/1ceccf4efb4683b7feb890f45a32b5922b40ed3f/sessions/7393dbfda8387e43b6d5851f359b0c07db414973 + client.pause(1000); + } + // Focusing on the Embed Window - client.windowHandles((result) => { - const handle = result.value[0]; - client.switchWindow(handle); + windowHandler.windowHandles((handles) => { + client.switchWindow(handles[0]); }); }, 'user posts a comment': (client) => { diff --git a/test/e2e/utils/SortedWindowHandler.js b/test/e2e/utils/SortedWindowHandler.js new file mode 100644 index 000000000..f57856814 --- /dev/null +++ b/test/e2e/utils/SortedWindowHandler.js @@ -0,0 +1,41 @@ + +/** + * SortedWindowHandler assists in making e2e tests more robust by returning + * deterministic window handles. An instance must be created before new windows + * are created and windowHandles must be called each time a window was created or + * closed. + */ +class SortedWindowHandler { + + /** + * Constructor, must be called before new windows were created. + */ + constructor(client) { + this.client = client; + this.client.windowHandles((result) => { + this.handles = result.value; + if (this.handles.length > 2) { + throw new Error('SortedWindowHandler must be created before new windows were created.'); + } + }); + } + + /** + * windowHandles will call given `callback` with an array of window handles. + */ + windowHandles(callback) { + this.client.windowHandles((result) => { + this.handles = this.handles.filter((handle) => result.value.includes(handle)); + const remaining = result.value.filter((handle) => !this.handles.includes(handle)); + if (remaining.length === 1) { + this.handles.push(remaining[0]); + } + if (remaining.length > 1) { + throw new Error('Cannot detect new window handle, because more than one windows was created.'); + } + callback(this.handles); + }); + } +} + +module.exports = SortedWindowHandler; diff --git a/test/server/services/users.js b/test/server/services/users.js index dddd5eda8..b4a15e3ba 100644 --- a/test/server/services/users.js +++ b/test/server/services/users.js @@ -1,8 +1,11 @@ const UsersService = require('../../../services/users'); const SettingsService = require('../../../services/settings'); +const MailerService = require('../../../services/mailer'); const chai = require('chai'); chai.use(require('chai-as-promised')); +const sinon = require('sinon'); +chai.use(require('sinon-chai')); const expect = chai.expect; describe('services.UsersService', () => { @@ -15,7 +18,7 @@ describe('services.UsersService', () => { mockUsers = await UsersService.createLocalUsers([{ email: 'stampi@gmail.com', username: 'Stampi', - password: '1Coral!-' + password: '1Coral!-', }, { email: 'sockmonster@gmail.com', username: 'Sockmonster', @@ -25,6 +28,12 @@ describe('services.UsersService', () => { username: 'Marvel', password: '3Coral!3' }]); + + sinon.spy(MailerService, 'sendSimple'); + }); + + afterEach(() => { + MailerService.sendSimple.restore(); }); describe('#findById()', () => { @@ -149,7 +158,11 @@ describe('services.UsersService', () => { .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('status', 'ACTIVE'); + }) + .then(() => { + expect(MailerService.sendSimple).to.not.have.been.called; }); + }); }); @@ -188,6 +201,12 @@ describe('services.UsersService', () => { .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('status', 'BANNED'); + }) + .then(() => { + expect(MailerService.sendSimple).to.have.been.calledWithMatch({ + template: 'banned', + to: mockUsers[0].profiles[0].id + }); }); }); diff --git a/yarn.lock b/yarn.lock index 6ea60fc82..7996e549f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7953,9 +7953,9 @@ select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" -selenium-standalone@6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.9.0.tgz#5805f403c82b48b4e136d8a97f1832aad7852bd5" +selenium-standalone@^6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.11.0.tgz#eb21ff65f3b2ece75061b35d4f9e7572ac0280c2" dependencies: async "^2.1.4" commander "^2.9.0"