diff --git a/.github/workflows/.tests-python.yml b/.github/workflows/.tests-python.yml index e019e23a..894df0b4 100644 --- a/.github/workflows/.tests-python.yml +++ b/.github/workflows/.tests-python.yml @@ -68,6 +68,14 @@ jobs: --health-retries 5 selenium: image: selenium/standalone-firefox + mailhog: + image: mailhog/mailhog:latest + redis: + image: redis:latest + env: + APP_SETTINGS: fittrackee.config.End2EndTestingConfig + EMAIL_URL: "smtp://mailhog:1025" + REDIS_URL: "redis://redis:6379" steps: - uses: actions/checkout@v2 - name: Install Poetry and Dependencies @@ -83,4 +91,5 @@ jobs: setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 & export TEST_APP_URL=http://$(hostname --ip-address):5000 sleep 5 + nohup flask worker --processes=1 >> nohup.out 2>&1 & pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444 diff --git a/e2e/test_login.py b/e2e/test_login.py index 2bf47e5e..316ce783 100644 --- a/e2e/test_login.py +++ b/e2e/test_login.py @@ -1,9 +1,4 @@ -from .utils import ( - TEST_URL, - assert_navbar, - login_valid_user, - register_valid_user_and_logout, -) +from .utils import TEST_URL, login_valid_user, register_valid_user_and_logout URL = f'{TEST_URL}/login' @@ -34,10 +29,20 @@ class TestLogin: assert 'Register' in links[0].text assert links[1].tag_name == 'a' assert 'Forgot password?' in links[1].text + assert links[2].tag_name == 'a' + assert "Didn't received instructions?" in links[2].text def test_user_can_log_in(self, selenium): user = register_valid_user_and_logout(selenium) login_valid_user(selenium, user) - assert_navbar(selenium, user) + nav = selenium.find_element_by_id('nav').text + assert 'Register' not in nav + assert 'Login' not in nav + assert 'Dashboard' in nav + assert 'Workouts' in nav + assert 'Statistics' in nav + assert 'Add a workout' in nav + assert user['username'] in nav + assert 'Logout' in nav diff --git a/e2e/test_profile.py b/e2e/test_profile.py index 6159e980..0abd8c22 100644 --- a/e2e/test_profile.py +++ b/e2e/test_profile.py @@ -1,13 +1,15 @@ -from .utils import TEST_URL, register_valid_user - -URL = f'{TEST_URL}/profile' +from .utils import register_valid_user class TestProfile: def test_it_displays_user_profile(self, selenium): user = register_valid_user(selenium) - selenium.get(URL) + app_menu = selenium.find_element_by_class_name('nav-items-user-menu') + profile_link = app_menu.find_elements_by_class_name('nav-item')[1] + profile_link.click() + selenium.implicitly_wait(1) + user_header = selenium.find_element_by_class_name('user-header') assert user['username'] in user_header.text assert '0\nworkouts' in user_header.text diff --git a/e2e/test_registration.py b/e2e/test_registration.py index 8d546aaa..1bb7f3fb 100644 --- a/e2e/test_registration.py +++ b/e2e/test_registration.py @@ -1,9 +1,7 @@ from .utils import ( TEST_URL, - assert_navbar, random_string, register, - register_valid_user, register_valid_user_and_logout, ) @@ -24,18 +22,39 @@ class TestRegistration: assert inputs[2].get_attribute('id') == 'password' assert inputs[2].get_attribute('type') == 'password' + form_infos = selenium.find_elements_by_class_name('form-info') + assert len(form_infos) == 3 + assert form_infos[0].text == ( + '3 to 30 characters required, only alphanumeric characters and ' + 'the underscore character "_" allowed.' + ) + assert form_infos[1].text == 'Enter a valid email address.' + assert form_infos[2].text == 'At least 8 characters required.' + button = selenium.find_element_by_tag_name('button') assert button.get_attribute('type') == 'submit' assert 'Register' in button.text - link = selenium.find_element_by_class_name('links') - assert link.tag_name == 'a' - assert 'Login' in link.text + links = selenium.find_elements_by_class_name('links') + assert links[0].tag_name == 'a' + assert 'Login' in links[0].text + assert links[1].tag_name == 'a' + assert "Didn't received instructions?" in links[1].text def test_user_can_register(self, selenium): - user = register_valid_user(selenium) + user = { + 'username': random_string(), + 'email': f'{random_string()}@example.com', + 'password': 'p@ssw0rd', + } - assert_navbar(selenium, user) + register(selenium, user) + + message = selenium.find_element_by_class_name('success-message').text + assert ( + 'A link to activate your account has been ' + 'emailed to the address provided.' + ) in message def test_user_can_not_register_with_invalid_email(self, selenium): user_name = random_string() @@ -62,14 +81,19 @@ class TestRegistration: assert selenium.current_url == URL errors = selenium.find_element_by_class_name('error-message').text - assert 'Sorry, that user already exists.' in errors + assert 'Sorry, that username is already taken.' in errors - def test_user_can_not_register_if_email_is_already_taken(self, selenium): + def test_user_does_not_return_error_if_email_is_already_taken( + self, selenium + ): user = register_valid_user_and_logout(selenium) user['username'] = random_string() register(selenium, user) - assert selenium.current_url == URL - errors = selenium.find_element_by_class_name('error-message').text - assert 'Sorry, that user already exists.' in errors + assert selenium.current_url == f'{TEST_URL}/login' + message = selenium.find_element_by_class_name('success-message').text + assert ( + 'A link to activate your account has been ' + 'emailed to the address provided.' + ) in message diff --git a/e2e/utils.py b/e2e/utils.py index 5ca69928..67d0431d 100644 --- a/e2e/utils.py +++ b/e2e/utils.py @@ -1,18 +1,25 @@ import os import random +import re import string +import time +import requests from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait +from urllib3.util import parse_url TEST_APP_URL = os.getenv('TEST_APP_URL') TEST_CLIENT_URL = os.getenv('TEST_CLIENT_URL') E2E_ARGS = os.getenv('E2E_ARGS') TEST_URL = TEST_CLIENT_URL if E2E_ARGS == 'client' else TEST_APP_URL +EMAIL_URL = os.getenv('EMAIL_URL', 'smtp://none:none@0.0.0.0:1025') +parsed_email_url = parse_url(EMAIL_URL) +EMAIL_API_URL = f'http://{parsed_email_url.host}:8025' def random_string(length=8): - return ''.join(random.choice(string.ascii_letters) for x in range(length)) + return ''.join(random.choice(string.ascii_letters) for _ in range(length)) def register(selenium, user): @@ -47,40 +54,34 @@ def register_valid_user(selenium): 'password': 'p@ssw0rd', } register(selenium, user) - WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register")) + WebDriverWait(selenium, 30).until(EC.url_changes(f"{TEST_URL}/register")) + confirm_account(selenium, user) return user def register_valid_user_and_logout(selenium): - user_name = random_string() - user = { - 'username': user_name, - 'email': f'{user_name}@example.com', - 'password': 'p@ssw0rd', - } - register(selenium, user) - WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register")) - + user = register_valid_user(selenium) user_menu = selenium.find_element_by_class_name('nav-items-user-menu') logout_link = user_menu.find_elements_by_class_name('nav-item')[2] logout_link.click() - selenium.implicitly_wait(1) return user +def confirm_account(selenium, user): + time.sleep(1) + response = requests.get( + f"{EMAIL_API_URL}/api/v2/search?kind=to&query={user['email']}" + ) + response.raise_for_status() + results = response.json() + message = results['items'][0]['Content']['Body'] + link = re.search(r'Verify your email: (.+?)\r\n', message).groups()[0] + link = link.replace('http://0.0.0.0:5000', TEST_URL) + selenium.get(link) + WebDriverWait(selenium, 15).until(EC.url_changes(link)) + + def login_valid_user(selenium, user): login(selenium, user) WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login")) return user - - -def assert_navbar(selenium, user): - nav = selenium.find_element_by_id('nav').text - assert 'Register' not in nav - assert 'Login' not in nav - assert 'Dashboard' in nav - assert 'Workouts' in nav - assert 'Statistics' in nav - assert 'Add a workout' in nav - assert user['username'] in nav - assert 'Logout' in nav diff --git a/fittrackee/config.py b/fittrackee/config.py index 668411b7..a210d7f1 100644 --- a/fittrackee/config.py +++ b/fittrackee/config.py @@ -12,7 +12,6 @@ else: class BaseConfig: - """Base configuration""" DEBUG = False TESTING = False @@ -49,8 +48,6 @@ class BaseConfig: class DevelopmentConfig(BaseConfig): - """Development configuration""" - DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') SECRET_KEY = 'development key' @@ -59,8 +56,6 @@ class DevelopmentConfig(BaseConfig): class TestingConfig(BaseConfig): - """Testing configuration""" - DEBUG = True TESTING = True SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') @@ -74,9 +69,11 @@ class TestingConfig(BaseConfig): SENDER_EMAIL = 'fittrackee@example.com' -class ProductionConfig(BaseConfig): - """Production configuration""" +class End2EndTestingConfig(TestingConfig): + DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://') + +class ProductionConfig(BaseConfig): DEBUG = False # https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork # noqa SQLALCHEMY_ENGINE_OPTIONS = (