diff --git a/.circleci/config.yml b/.circleci/config.yml index 2906b00b..a3a42a64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,21 +3,57 @@ jobs: build: docker: - image: circleci/python:3.6.2-stretch-browsers + environment: + FLASK_CONFIG: testing + TEST_DATABASE_URL: postgresql://ubuntu@localhost/circle_test?sslmode=disable + - image: circleci/postgres:9.6.5-alpine-ram + environment: + POSTGRES_USER: ubuntu + POSTGRES_DB: circle_test + POSTGRES_PASSWORD: "" steps: - checkout + - run: mkdir test-reports + - run: + name: Download Selenium + command: | + curl -O http://selenium-release.storage.googleapis.com/3.5/selenium-server-standalone-3.5.3.jar + - run: + name: Start Selenium + command: | + java -jar selenium-server-standalone-3.5.3.jar -log test-reports/selenium.log + background: true - restore_cache: - key: -v1-{{ checksum "requirements/dev.txt" }} + key: deps1-{{ .Branch }}-{{ checksum "requirements/dev.txt" }} - run: command: | python3 -m venv venv . venv/bin/activate pip install -r requirements/dev.txt + - run: + name: setup Heroku + command: bash .circleci/setup-heroku.sh - save_cache: - key: -v1-{{ checksum "requirements/dev.txt" }} + key: deps1-{{ .Branch }}-{{ checksum "requirements/dev.txt" }} paths: - "venv" - run: command: | . venv/bin/activate python manage.py test - + - store_artifacts: + path: test-reports/ + destination: tr1 + - store_test_results: + path: test-reports/ + - add_ssh_keys: + fingerprints: + - "48:a0:87:54:ca:75:32:12:c6:9e:a2:77:a4:7a:08:a4" + - deploy: + name: Deploy Master to Heroku + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; then + git push heroku master + heroku run python manage.py deploy + heroku restart + fi diff --git a/tests/sub_tests/__init__.py b/tests/sub_tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/sub_tests/test_api.py b/tests/sub_tests/test_api.py deleted file mode 100644 index db297ffe..00000000 --- a/tests/sub_tests/test_api.py +++ /dev/null @@ -1,265 +0,0 @@ -import unittest -import json -import re -from base64 import b64encode -from flask import url_for -from app import create_app, db -from app.models import User, Role, Post, Comment - - -class APITestCase(unittest.TestCase): - def setUp(self): - self.app = create_app('testing') - self.app_context = self.app.app_context() - self.app_context.push() - db.create_all() - Role.insert_roles() - self.client = self.app.test_client() - - def tearDown(self): - db.session.remove() - db.drop_all() - self.app_context.pop() - - def get_api_headers(self, username, password): - return { - 'Authorization': 'Basic ' + b64encode( - (username + ':' + password).encode('utf-8')).decode('utf-8'), - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - - def test_404(self): - response = self.client.get( - '/wrong/url', - headers=self.get_api_headers('email', 'password')) - self.assertTrue(response.status_code == 404) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['error'] == 'not found') - - def test_no_auth(self): - response = self.client.get(url_for('api.get_posts'), - content_type='application/json') - self.assertTrue(response.status_code == 200) - - def test_bad_auth(self): - # add a user - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u = User(email='john@example.com', password='cat', confirmed=True, - role=r) - db.session.add(u) - db.session.commit() - - # authenticate with bad password - response = self.client.get( - url_for('api.get_posts'), - headers=self.get_api_headers('john@example.com', 'dog')) - self.assertTrue(response.status_code == 401) - - def test_token_auth(self): - # add a user - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u = User(email='john@example.com', password='cat', confirmed=True, - role=r) - db.session.add(u) - db.session.commit() - - # issue a request with a bad token - response = self.client.get( - url_for('api.get_posts'), - headers=self.get_api_headers('bad-token', '')) - self.assertTrue(response.status_code == 401) - - # get a token - response = self.client.get( - url_for('api.get_token'), - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertIsNotNone(json_response.get('token')) - token = json_response['token'] - - # issue a request with the token - response = self.client.get( - url_for('api.get_posts'), - headers=self.get_api_headers(token, '')) - self.assertTrue(response.status_code == 200) - - def test_anonymous(self): - response = self.client.get( - url_for('api.get_posts'), - headers=self.get_api_headers('', '')) - self.assertTrue(response.status_code == 200) - - def test_unconfirmed_account(self): - # add an unconfirmed user - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u = User(email='john@example.com', password='cat', confirmed=False, - role=r) - db.session.add(u) - db.session.commit() - - # get list of posts with the unconfirmed account - response = self.client.get( - url_for('api.get_posts'), - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 403) - - def test_posts(self): - # add a user - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u = User(email='john@example.com', password='cat', confirmed=True, - role=r) - db.session.add(u) - db.session.commit() - - # write an empty post - response = self.client.post( - url_for('api.new_post'), - headers=self.get_api_headers('john@example.com', 'cat'), - data=json.dumps({'body': ''})) - self.assertTrue(response.status_code == 400) - - # write a post - response = self.client.post( - url_for('api.new_post'), - headers=self.get_api_headers('john@example.com', 'cat'), - data=json.dumps({'body': 'body of the *blog* post'})) - self.assertTrue(response.status_code == 201) - url = response.headers.get('Location') - self.assertIsNotNone(url) - - # get the new post - response = self.client.get( - url, - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['url'] == url) - self.assertTrue(json_response['body'] == 'body of the *blog* post') - self.assertTrue(json_response['body_html'] == - '

body of the blog post

') - json_post = json_response - - # get the post from the user - response = self.client.get( - url_for('api.get_user_posts', id=u.id), - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertIsNotNone(json_response.get('posts')) - self.assertTrue(json_response.get('count', 0) == 1) - self.assertTrue(json_response['posts'][0] == json_post) - - # get the post from the user as a follower - response = self.client.get( - url_for('api.get_user_followed_posts', id=u.id), - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertIsNotNone(json_response.get('posts')) - self.assertTrue(json_response.get('count', 0) == 1) - self.assertTrue(json_response['posts'][0] == json_post) - - # edit post - response = self.client.put( - url, - headers=self.get_api_headers('john@example.com', 'cat'), - data=json.dumps({'body': 'updated body'})) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['url'] == url) - self.assertTrue(json_response['body'] == 'updated body') - self.assertTrue(json_response['body_html'] == '

updated body

') - - def test_users(self): - # add two users - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u1 = User(email='john@example.com', username='john', - password='cat', confirmed=True, role=r) - u2 = User(email='susan@example.com', username='susan', - password='dog', confirmed=True, role=r) - db.session.add_all([u1, u2]) - db.session.commit() - - # get users - response = self.client.get( - url_for('api.get_user', id=u1.id), - headers=self.get_api_headers('susan@example.com', 'dog')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['username'] == 'john') - response = self.client.get( - url_for('api.get_user', id=u2.id), - headers=self.get_api_headers('susan@example.com', 'dog')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['username'] == 'susan') - - def test_comments(self): - # add two users - r = Role.query.filter_by(name='User').first() - self.assertIsNotNone(r) - u1 = User(email='john@example.com', username='john', - password='cat', confirmed=True, role=r) - u2 = User(email='susan@example.com', username='susan', - password='dog', confirmed=True, role=r) - db.session.add_all([u1, u2]) - db.session.commit() - - # add a post - post = Post(body='body of the post', author=u1) - db.session.add(post) - db.session.commit() - - # write a comment - response = self.client.post( - url_for('api.new_post_comment', id=post.id), - headers=self.get_api_headers('susan@example.com', 'dog'), - data=json.dumps({'body': 'Good [post](http://example.com)!'})) - self.assertTrue(response.status_code == 201) - json_response = json.loads(response.data.decode('utf-8')) - url = response.headers.get('Location') - self.assertIsNotNone(url) - self.assertTrue(json_response['body'] == - 'Good [post](http://example.com)!') - self.assertTrue( - re.sub('<.*?>', '', json_response['body_html']) == 'Good post!') - - # get the new comment - response = self.client.get( - url, - headers=self.get_api_headers('john@example.com', 'cat')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertTrue(json_response['url'] == url) - self.assertTrue(json_response['body'] == - 'Good [post](http://example.com)!') - - # add another comment - comment = Comment(body='Thank you!', author=u1, post=post) - db.session.add(comment) - db.session.commit() - - # get the two comments from the post - response = self.client.get( - url_for('api.get_post_comments', id=post.id), - headers=self.get_api_headers('susan@example.com', 'dog')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertIsNotNone(json_response.get('comments')) - self.assertTrue(json_response.get('count', 0) == 2) - - # get all the comments - response = self.client.get( - url_for('api.get_comments', id=post.id), - headers=self.get_api_headers('susan@example.com', 'dog')) - self.assertTrue(response.status_code == 200) - json_response = json.loads(response.data.decode('utf-8')) - self.assertIsNotNone(json_response.get('comments')) - self.assertTrue(json_response.get('count', 0) == 2) diff --git a/tests/sub_tests/test_basics.py b/tests/sub_tests/test_basics.py deleted file mode 100644 index 0fdf4983..00000000 --- a/tests/sub_tests/test_basics.py +++ /dev/null @@ -1,22 +0,0 @@ -import unittest -from flask import current_app -from app import create_app, db - - -class BasicsTestCase(unittest.TestCase): - def setUp(self): - self.app = create_app('testing') - self.app_context = self.app.app_context() - self.app_context.push() - db.create_all() - - def tearDown(self): - db.session.remove() - db.drop_all() - self.app_context.pop() - - def test_app_exists(self): - self.assertFalse(current_app is None) - - def test_app_is_testing(self): - self.assertTrue(current_app.config['TESTING']) diff --git a/tests/sub_tests/test_client.py b/tests/sub_tests/test_client.py deleted file mode 100644 index b5955208..00000000 --- a/tests/sub_tests/test_client.py +++ /dev/null @@ -1,54 +0,0 @@ -import re -import unittest -from flask import url_for -from app import create_app, db -from app.models import User, Role - -class FlaskClientTestCase(unittest.TestCase): - def setUp(self): - self.app = create_app('testing') - self.app_context = self.app.app_context() - self.app_context.push() - db.create_all() - Role.insert_roles() - self.client = self.app.test_client(use_cookies=True) - - def tearDown(self): - db.session.remove() - db.drop_all() - self.app_context.pop() - - def test_home_page(self): - response = self.client.get(url_for('main.index')) - self.assertTrue(b'Stranger' in response.data) - - def test_register_and_login(self): - # register a new account - response = self.client.post(url_for('auth.register'), data={ - 'email': 'john@example.com', - 'username': 'john', - 'password': 'cat', - 'password2': 'cat' - }) - self.assertTrue(response.status_code == 302) - - # login with the new account - response = self.client.post(url_for('auth.login'), data={ - 'email': 'john@example.com', - 'password': 'cat' - }, follow_redirects=True) - self.assertTrue(re.search(b'Hello,\s+john!', response.data)) - self.assertTrue( - b'You have not confirmed your account yet' in response.data) - - # send a confirmation token - user = User.query.filter_by(email='john@example.com').first() - token = user.generate_confirmation_token() - response = self.client.get(url_for('auth.confirm', token=token), - follow_redirects=True) - self.assertTrue( - b'You have confirmed your account' in response.data) - - # log out - response = self.client.get(url_for('auth.logout'), follow_redirects=True) - self.assertTrue(b'You have been logged out' in response.data) diff --git a/tests/sub_tests/test_selenium.py b/tests/sub_tests/test_selenium.py deleted file mode 100644 index e89b25ba..00000000 --- a/tests/sub_tests/test_selenium.py +++ /dev/null @@ -1,93 +0,0 @@ -import re -import threading -import time -import unittest -from selenium import webdriver -from app import create_app, db -from app.models import Role, User, Post - - -class SeleniumTestCase(unittest.TestCase): - client = None - - @classmethod - def setUpClass(cls): - # start Chrome - try: - cls.client = webdriver.Chrome(service_args=["--verbose", "--log-path=test-reports/chrome.log"]) - except: - pass - - # skip these tests if the browser could not be started - if cls.client: - # create the application - cls.app = create_app('testing') - cls.app_context = cls.app.app_context() - cls.app_context.push() - - # suppress logging to keep unittest output clean - import logging - logger = logging.getLogger('werkzeug') - logger.setLevel("ERROR") - - # create the database and populate with some fake data - db.create_all() - Role.insert_roles() - User.generate_fake(10) - Post.generate_fake(10) - - # add an administrator user - admin_role = Role.query.filter_by(permissions=0xff).first() - admin = User(email='john@example.com', - username='john', password='cat', - role=admin_role, confirmed=True) - db.session.add(admin) - db.session.commit() - - # start the Flask server in a thread - threading.Thread(target=cls.app.run).start() - - # give the server a second to ensure it is up - time.sleep(1) - - @classmethod - def tearDownClass(cls): - if cls.client: - # stop the flask server and the browser - cls.client.get('http://localhost:5000/shutdown') - cls.client.close() - - # destroy database - db.drop_all() - db.session.remove() - - # remove application context - cls.app_context.pop() - - def setUp(self): - if not self.client: - self.skipTest('Web browser not available') - - def tearDown(self): - pass - - def test_admin_home_page(self): - # navigate to home page - self.client.get('http://localhost:5000/') - self.assertTrue(re.search('Hello,\s+Stranger!', - self.client.page_source)) - - # navigate to login page - self.client.find_element_by_link_text('Log In').click() - self.assertTrue('

Login

' in self.client.page_source) - - # login - self.client.find_element_by_name('email').\ - send_keys('john@example.com') - self.client.find_element_by_name('password').send_keys('cat') - self.client.find_element_by_name('submit').click() - self.assertTrue(re.search('Hello,\s+john!', self.client.page_source)) - - # navigate to the user's profile page - self.client.find_element_by_link_text('Profile').click() - self.assertTrue('

john

' in self.client.page_source) diff --git a/tests/sub_tests/test_user_model.py b/tests/sub_tests/test_user_model.py deleted file mode 100644 index 2741b415..00000000 --- a/tests/sub_tests/test_user_model.py +++ /dev/null @@ -1,199 +0,0 @@ -import unittest -import time -from datetime import datetime -from app import create_app, db -from app.models import User, AnonymousUser, Role, Permission, Follow - - -class UserModelTestCase(unittest.TestCase): - def setUp(self): - self.app = create_app('testing') - self.app_context = self.app.app_context() - self.app_context.push() - db.create_all() - Role.insert_roles() - - def tearDown(self): - db.session.remove() - db.drop_all() - self.app_context.pop() - - def test_password_setter(self): - u = User(password='cat') - self.assertTrue(u.password_hash is not None) - - def test_no_password_getter(self): - u = User(password='cat') - with self.assertRaises(AttributeError): - u.password - - def test_password_verification(self): - u = User(password='cat') - self.assertTrue(u.verify_password('cat')) - self.assertFalse(u.verify_password('dog')) - - def test_password_salts_are_random(self): - u = User(password='cat') - u2 = User(password='cat') - self.assertTrue(u.password_hash != u2.password_hash) - - def test_valid_confirmation_token(self): - u = User(password='cat') - db.session.add(u) - db.session.commit() - token = u.generate_confirmation_token() - self.assertTrue(u.confirm(token)) - - def test_invalid_confirmation_token(self): - u1 = User(password='cat') - u2 = User(password='dog') - db.session.add(u1) - db.session.add(u2) - db.session.commit() - token = u1.generate_confirmation_token() - self.assertFalse(u2.confirm(token)) - - def test_expired_confirmation_token(self): - u = User(password='cat') - db.session.add(u) - db.session.commit() - token = u.generate_confirmation_token(1) - time.sleep(2) - self.assertFalse(u.confirm(token)) - - def test_valid_reset_token(self): - u = User(password='cat') - db.session.add(u) - db.session.commit() - token = u.generate_reset_token() - self.assertTrue(u.reset_password(token, 'dog')) - self.assertTrue(u.verify_password('dog')) - - def test_invalid_reset_token(self): - u1 = User(password='cat') - u2 = User(password='dog') - db.session.add(u1) - db.session.add(u2) - db.session.commit() - token = u1.generate_reset_token() - self.assertFalse(u2.reset_password(token, 'horse')) - self.assertTrue(u2.verify_password('dog')) - - def test_valid_email_change_token(self): - u = User(email='john@example.com', password='cat') - db.session.add(u) - db.session.commit() - token = u.generate_email_change_token('susan@example.org') - self.assertTrue(u.change_email(token)) - self.assertTrue(u.email == 'susan@example.org') - - def test_invalid_email_change_token(self): - u1 = User(email='john@example.com', password='cat') - u2 = User(email='susan@example.org', password='dog') - db.session.add(u1) - db.session.add(u2) - db.session.commit() - token = u1.generate_email_change_token('david@example.net') - self.assertFalse(u2.change_email(token)) - self.assertTrue(u2.email == 'susan@example.org') - - def test_duplicate_email_change_token(self): - u1 = User(email='john@example.com', password='cat') - u2 = User(email='susan@example.org', password='dog') - db.session.add(u1) - db.session.add(u2) - db.session.commit() - token = u2.generate_email_change_token('john@example.com') - self.assertFalse(u2.change_email(token)) - self.assertTrue(u2.email == 'susan@example.org') - - def test_roles_and_permissions(self): - u = User(email='john@example.com', password='cat') - self.assertTrue(u.can(Permission.WRITE_ARTICLES)) - self.assertFalse(u.can(Permission.MODERATE_COMMENTS)) - - def test_anonymous_user(self): - u = AnonymousUser() - self.assertFalse(u.can(Permission.FOLLOW)) - - def test_timestamps(self): - u = User(password='cat') - db.session.add(u) - db.session.commit() - self.assertTrue( - (datetime.utcnow() - u.member_since).total_seconds() < 3) - self.assertTrue( - (datetime.utcnow() - u.last_seen).total_seconds() < 3) - - def test_ping(self): - u = User(password='cat') - db.session.add(u) - db.session.commit() - time.sleep(2) - last_seen_before = u.last_seen - u.ping() - self.assertTrue(u.last_seen > last_seen_before) - - def test_gravatar(self): - u = User(email='john@example.com', password='cat') - with self.app.test_request_context('/'): - gravatar = u.gravatar() - gravatar_256 = u.gravatar(size=256) - gravatar_pg = u.gravatar(rating='pg') - gravatar_retro = u.gravatar(default='retro') - with self.app.test_request_context('/', base_url='https://example.com'): - gravatar_ssl = u.gravatar() - self.assertTrue('http://www.gravatar.com/avatar/' + - 'd4c74594d841139328695756648b6bd6'in gravatar) - self.assertTrue('s=256' in gravatar_256) - self.assertTrue('r=pg' in gravatar_pg) - self.assertTrue('d=retro' in gravatar_retro) - self.assertTrue('https://secure.gravatar.com/avatar/' + - 'd4c74594d841139328695756648b6bd6' in gravatar_ssl) - -# def test_follows(self): -# u1 = User(email='john@example.com', password='cat') -# u2 = User(email='susan@example.org', password='dog') -# db.session.add(u1) -# db.session.add(u2) -# db.session.commit() -# self.assertFalse(u1.is_following(u2)) -# self.assertFalse(u1.is_followed_by(u2)) -# timestamp_before = datetime.utcnow() -# u1.follow(u2) -# db.session.add(u1) -# db.session.commit() -# timestamp_after = datetime.utcnow() -# self.assertTrue(u1.is_following(u2)) -# self.assertFalse(u1.is_followed_by(u2)) -# self.assertTrue(u2.is_followed_by(u1)) -# self.assertTrue(u1.followed.count() == 2) -# self.assertTrue(u2.followers.count() == 2) -# f = u1.followed.all()[-1] -# self.assertTrue(f.followed == u2) -# self.assertTrue(timestamp_before <= f.timestamp <= timestamp_after) -# f = u2.followers.all()[-1] -# self.assertTrue(f.follower == u1) -# u1.unfollow(u2) -# db.session.add(u1) -# db.session.commit() -# self.assertTrue(u1.followed.count() == 1) -# self.assertTrue(u2.followers.count() == 1) -# self.assertTrue(Follow.query.count() == 2) -# u2.follow(u1) -# db.session.add(u1) -# db.session.add(u2) -# db.session.commit() -# db.session.delete(u2) -# db.session.commit() -# self.assertTrue(Follow.query.count() == 1) - - def test_to_json(self): - u = User(email='john@example.com', password='cat') - db.session.add(u) - db.session.commit() - json_user = u.to_json() - expected_keys = ['url', 'username', 'member_since', 'last_seen', - 'posts', 'followed_posts', 'post_count'] - self.assertEqual(sorted(json_user.keys()), sorted(expected_keys)) - self.assertTrue('api/v1.0/users/' in json_user['url'])