import os from datetime import datetime, timedelta, timezone from shutil import copyfileobj from unittest.mock import Mock, patch import pytest from ZODB.blob import Blob from pyramid.testing import DummyRequest from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from webob.multidict import MultiDict from ow.models.root import OpenWorkouts from ow.models.user import User from ow.models.workout import Workout from ow.views.renderers import OWFormRenderer import ow.views.user as user_views class TestUserViews(object): @pytest.fixture def root(self): root = OpenWorkouts() root['john'] = User(firstname='John', lastname='Doe', email='john.doe@example.net') root['john'].password = 's3cr3t' workout = Workout( start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc), duration=timedelta(minutes=60), distance=30 ) root['john'].add_workout(workout) return root @pytest.fixture def dummy_request(self, root): request = DummyRequest() request.root = root return request @pytest.fixture def profile_post_request(self, root): """ This is a valid POST request to update an user profile. Form will validate, but nothing will be really updated/changed. """ user = root['john'] request = DummyRequest() request.root = root request.method = 'POST' request.POST = MultiDict({ 'submit': True, 'firstname': user.firstname, 'lastname': user.lastname, 'email': user.email, 'bio': user.bio, 'weight': user.weight, 'height': user.height, 'gender': user.gender, 'birth_date': user.birth_date, 'picture': user.picture, }) return request @pytest.fixture def passwd_post_request(self, root): """ This is a valid POST request to change the user password, but the form will not validate (empty fields) """ request = DummyRequest() request.root = root request.method = 'POST' request.POST = MultiDict({ 'submit': True, 'old_password': '', 'password': '', 'password_confirm': '' }) return request @pytest.fixture def signup_post_request(self, root): """ This is a valid POST request to signup a new user. """ request = DummyRequest() request.root = root request.method = 'POST' request.POST = MultiDict({ 'submit': True, 'username': 'JackBlack', 'email': 'jack.black@example.net', 'firstname': 'Jack', 'lastname': 'Black', 'password': 'j4ck s3cr3t', 'password_confirm': 'j4ck s3cr3t' }) return request def test_dashboard_redirect_unauthenticated(self, root): """ Anoymous access to the root object, send the user to the login page. Instead of reusing the DummyRequest from the request fixture, we do Mock fully the request here, because we need to use authenticated_userid, which cannot be easily set in the DummyRequest """ request = DummyRequest() request.root = root response = user_views.dashboard_redirect(root, request) assert isinstance(response, HTTPFound) assert response.location == request.resource_url(root, 'login') def test_dashboard_redirect_authenticated(self, root): """ Authenticated user accesing the root object, send the user to her dashboard Instead of reusing the DummyRequest from the request fixture, we do Mock fully the request here, because we need to use authenticated_userid, which cannot be easily set in the DummyRequest """ request = Mock() request.root = root request.authenticated_userid = 'john' request.resource_url.return_value = '/dashboard' response = user_views.dashboard_redirect(root, request) assert isinstance(response, HTTPFound) assert response.location == '/dashboard' def test_dashboard(self, dummy_request): """ Renders the user dashboard """ request = dummy_request user = request.root['john'] response = user_views.dashboard(user, request) assert response == {} def test_profile(self, dummy_request): """ Renders the user profile page """ request = dummy_request user = request.root['john'] response = user_views.profile(user, request) assert response == {} def test_login_get(self, dummy_request): """ GET request to access the login page """ request = dummy_request response = user_views.login(request.root, request) assert response['message'] == '' assert response['username'] == '' assert response['password'] == '' assert response['redirect_url'] == request.resource_url(request.root) def test_login_get_return_to(self, dummy_request): """ GET request to access the login page, if there is a page set to where the user should be sent to, the response "redirect_url" key will have such url """ request = dummy_request workout = request.root['john'].workouts()[0] workout_url = request.resource_url(workout) request.params['return_to'] = workout_url response = user_views.login(request.root, request) assert response['redirect_url'] == workout_url def test_login_post_bad_username(self, dummy_request): request = dummy_request request.method = 'POST' request.POST['submit'] = True request.POST['username'] = 'jack' response = user_views.login(request.root, request) assert response['message'] == u'Bad username' def test_login_post_bad_password(self, dummy_request): request = dummy_request request.method = 'POST' request.POST['submit'] = True request.POST['username'] = 'john' request.POST['password'] = 'badpassword' response = user_views.login(request.root, request) assert response['message'] == u'Bad password' @patch('ow.views.user.remember') def test_login_post_ok(self, rem, dummy_request): request = dummy_request request.method = 'POST' request.POST['submit'] = True request.POST['username'] = 'john' request.POST['password'] = 's3cr3t' response = user_views.login(request.root, request) assert isinstance(response, HTTPFound) assert rem.called assert response.location == request.resource_url(request.root) @patch('ow.views.user.forget') def test_logout(self, forg, dummy_request): request = dummy_request response = user_views.logout(request.root, request) assert isinstance(response, HTTPFound) assert forg.called assert response.location == request.resource_url(request.root) extensions = ('png', 'jpg', 'jpeg', 'gif') @pytest.mark.parametrize('extension', extensions) def test_profile_picture(self, extension, dummy_request): """ GET request to get the profile picture of an user. """ request = dummy_request # Get the user user = request.root['john'] # Get the path to the image, then open it and copy it to a new Blob # object path = 'fixtures/image.' + extension image_path = os.path.join( os.path.dirname(os.path.dirname(__file__)), path) blob = Blob() with open(image_path, 'rb') as infile, blob.open('w') as out: infile.seek(0) copyfileobj(infile, out) # Set the blob with the picture user.picture = blob # Call the profile_picture view response = user_views.profile_picture(user, request) assert isinstance(response, Response) assert response.status_int == 200 assert response.content_type == 'image' def test_edit_profile_get(self, dummy_request): """ GET request to the edit profile page, returns the form ready to be rendered """ request = dummy_request user = request.root['john'] response = user_views.edit_profile(user, request) assert isinstance(response['form'], OWFormRenderer) # no errors in the form (first load) assert response['form'].errorlist() == '' # the form carries along the proper data keys, taken from the # loaded user profile data = ['firstname', 'lastname', 'email', 'bio', 'birth_date', 'height', 'weight', 'gender'] assert list(response['form'].data.keys()) == data # and check the email to see data is properly loaded assert response['form'].data['email'] == 'john.doe@example.net' def test_edit_profile_post_ok(self, profile_post_request): request = profile_post_request user = request.root['john'] # Update the bio field bio = 'Some text about this user' request.POST['bio'] = bio response = user_views.edit_profile(user, request) assert isinstance(response, HTTPFound) assert response.location == request.resource_url(user, 'profile') assert user.bio == bio def test_edit_profile_post_missing_required(self, profile_post_request): request = profile_post_request request.POST['email'] = '' user = request.root['john'] response = user_views.edit_profile(user, request) assert isinstance(response['form'], OWFormRenderer) # error on the missing email field error = u'Please enter an email address' html_error = u'