source: OpenWorkouts-current/ow/tests/views/test_user.py @ fe6089a

currentfeature/docs
Last change on this file since fe6089a was fe6089a, checked in by borja <borja@…>, 5 years ago

Tests and coverage catch up.

  • Property mode set to 100644
File size: 19.6 KB
Line 
1import os
2from datetime import datetime, timedelta, timezone
3from shutil import copyfileobj
4from unittest.mock import Mock, patch
5
6import pytest
7
8from ZODB.blob import Blob
9
10from pyramid.testing import DummyRequest
11from pyramid.httpexceptions import HTTPFound
12from pyramid.response import Response
13
14from webob.multidict import MultiDict
15
16from ow.models.root import OpenWorkouts
17from ow.models.user import User
18from ow.models.workout import Workout
19from ow.views.renderers import OWFormRenderer
20import ow.views.user as user_views
21
22
23class TestUserViews(object):
24
25    @pytest.fixture
26    def john(self):
27        user = User(firstname='John', lastname='Doe',
28                    email='john.doe@example.net')
29        user.password = 's3cr3t'
30        return user
31
32    @pytest.fixture
33    def root(self, john):
34        root = OpenWorkouts()
35        root.add_user(john)
36        workout = Workout(
37            start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc),
38            duration=timedelta(minutes=60),
39            distance=30
40        )
41        john.add_workout(workout)
42        return root
43
44    @pytest.fixture
45    def dummy_request(self, root):
46        request = DummyRequest()
47        request.root = root
48        return request
49
50    @pytest.fixture
51    def profile_post_request(self, root, john):
52        """
53        This is a valid POST request to update an user profile.
54        Form will validate, but nothing will be really updated/changed.
55        """
56        user = john
57        request = DummyRequest()
58        request.root = root
59        request.method = 'POST'
60        request.POST = MultiDict({
61            'submit': True,
62            'firstname': user.firstname,
63            'lastname': user.lastname,
64            'email': user.email,
65            'bio': user.bio,
66            'weight': user.weight,
67            'height': user.height,
68            'gender': user.gender,
69            'birth_date': user.birth_date,
70            'picture': user.picture,
71            })
72        return request
73
74    @pytest.fixture
75    def passwd_post_request(self, root):
76        """
77        This is a valid POST request to change the user password, but
78        the form will not validate (empty fields)
79        """
80        request = DummyRequest()
81        request.root = root
82        request.method = 'POST'
83        request.POST = MultiDict({
84            'submit': True,
85            'old_password': '',
86            'password': '',
87            'password_confirm': ''
88            })
89        return request
90
91    @pytest.fixture
92    def signup_post_request(self, root):
93        """
94        This is a valid POST request to signup a new user.
95        """
96        request = DummyRequest()
97        request.root = root
98        request.method = 'POST'
99        request.POST = MultiDict({
100            'submit': True,
101            'nickname': 'JackBlack',
102            'email': 'jack.black@example.net',
103            'firstname': 'Jack',
104            'lastname': 'Black',
105            'password': 'j4ck s3cr3t',
106            'password_confirm': 'j4ck s3cr3t'
107            })
108        return request
109
110    def test_dashboard_redirect_unauthenticated(self, root):
111        """
112        Anoymous access to the root object, send the user to the login page.
113
114        Instead of reusing the DummyRequest from the request fixture, we do
115        Mock fully the request here, because we need to use
116        authenticated_userid, which cannot be easily set in the DummyRequest
117        """
118        request = DummyRequest()
119        request.root = root
120        response = user_views.dashboard_redirect(root, request)
121        assert isinstance(response, HTTPFound)
122        assert response.location == request.resource_url(root, 'login')
123
124    def test_dashboard_redirect_authenticated(self, root, john):
125        """
126        Authenticated user accesing the root object, send the user to her
127        dashboard
128
129        Instead of reusing the DummyRequest from the request fixture, we do
130        Mock fully the request here, because we need to use
131        authenticated_userid, which cannot be easily set in the DummyRequest
132        """
133        alt_request = DummyRequest()
134        request = Mock()
135        request.root = root
136        request.authenticated_userid = str(john.uid)
137        request.resource_url = alt_request.resource_url
138        response = user_views.dashboard_redirect(root, request)
139        assert isinstance(response, HTTPFound)
140        assert response.location == request.resource_url(john)
141        # if authenticated_userid is the id of an user that does not exist
142        # anymore, we send the user to the logout page
143        request.authenticated_userid = 'faked-uid'
144        response = user_views.dashboard_redirect(root, request)
145        assert isinstance(response, HTTPFound)
146        assert response.location == request.resource_url(root, 'logout')
147
148    def test_dashboard(self, dummy_request, john):
149        """
150        Renders the user dashboard
151        """
152        request = dummy_request
153        response = user_views.dashboard(john, request)
154        assert response == {}
155
156    def test_profile(self, dummy_request, john):
157        """
158        Renders the user profile page
159        """
160        request = dummy_request
161        response = user_views.profile(john, request)
162        assert response == {}
163
164    def test_login_get(self, dummy_request):
165        """
166        GET request to access the login page
167        """
168        request = dummy_request
169        response = user_views.login(request.root, request)
170        assert response['message'] == ''
171        assert response['email'] == ''
172        assert response['password'] == ''
173        assert response['redirect_url'] == request.resource_url(request.root)
174
175    def test_login_get_return_to(self, dummy_request, john):
176        """
177        GET request to access the login page, if there is a page set to where
178        the user should be sent to, the response "redirect_url" key will have
179        such url
180        """
181        request = dummy_request
182        workout = john.workouts()[0]
183        workout_url = request.resource_url(workout)
184        request.params['return_to'] = workout_url
185        response = user_views.login(request.root, request)
186        assert response['redirect_url'] == workout_url
187
188    def test_login_post_wrong_email(self, dummy_request):
189        request = dummy_request
190        request.method = 'POST'
191        request.POST['submit'] = True
192        request.POST['email'] = 'jack@example.net'
193        response = user_views.login(request.root, request)
194        assert response['message'] == u'Wrong email address'
195
196    def test_login_post_wrong_password(self, dummy_request):
197        request = dummy_request
198        request.method = 'POST'
199        request.POST['submit'] = True
200        request.POST['email'] = 'john.doe@example.net'
201        request.POST['password'] = 'badpassword'
202        response = user_views.login(request.root, request)
203        assert response['message'] == u'Wrong password'
204
205    @patch('ow.views.user.remember')
206    def test_login_post_ok(self, rem, dummy_request, john):
207        request = dummy_request
208        request.method = 'POST'
209        request.POST['submit'] = True
210        request.POST['email'] = 'john.doe@example.net'
211        request.POST['password'] = 's3cr3t'
212        response = user_views.login(request.root, request)
213        assert isinstance(response, HTTPFound)
214        assert rem.called
215        assert response.location == request.resource_url(john)
216
217    @patch('ow.views.user.forget')
218    def test_logout(self, forg, dummy_request):
219        request = dummy_request
220        response = user_views.logout(request.root, request)
221        assert isinstance(response, HTTPFound)
222        assert forg.called
223        assert response.location == request.resource_url(request.root)
224
225    extensions = ('png', 'jpg', 'jpeg', 'gif')
226
227    @pytest.mark.parametrize('extension', extensions)
228    def test_profile_picture(self, extension, dummy_request, john):
229        """
230        GET request to get the profile picture of an user.
231        """
232        request = dummy_request
233        # Get the user
234        user = john
235        # Get the path to the image, then open it and copy it to a new Blob
236        # object
237        path = 'fixtures/image.' + extension
238        image_path = os.path.join(
239            os.path.dirname(os.path.dirname(__file__)), path)
240        blob = Blob()
241        with open(image_path, 'rb') as infile, blob.open('w') as out:
242            infile.seek(0)
243            copyfileobj(infile, out)
244
245        # Set the blob with the picture
246        user.picture = blob
247
248        # Call the profile_picture view
249        response = user_views.profile_picture(user, request)
250        assert isinstance(response, Response)
251        assert response.status_int == 200
252        assert response.content_type == 'image'
253
254    def test_edit_profile_get(self, dummy_request, john):
255        """
256        GET request to the edit profile page, returns the form ready to
257        be rendered
258        """
259        request = dummy_request
260        user = john
261        response = user_views.edit_profile(user, request)
262        assert isinstance(response['form'], OWFormRenderer)
263        # no errors in the form (first load)
264        assert response['form'].errorlist() == ''
265        # the form carries along the proper data keys, taken from the
266        # loaded user profile
267        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
268                'birth_date', 'height', 'weight', 'gender', 'timezone']
269        assert list(response['form'].data.keys()) == data
270        # and check the email to see data is properly loaded
271        assert response['form'].data['email'] == 'john.doe@example.net'
272
273    def test_edit_profile_post_ok(self, profile_post_request, john):
274        request = profile_post_request
275        user = john
276        # Update the bio field
277        bio = 'Some text about this user'
278        request.POST['bio'] = bio
279        response = user_views.edit_profile(user, request)
280        assert isinstance(response, HTTPFound)
281        assert response.location == request.resource_url(user, 'profile')
282        assert user.bio == bio
283
284    def test_edit_profile_post_missing_required(
285            self, profile_post_request, john):
286        request = profile_post_request
287        request.POST['email'] = ''
288        user = john
289        response = user_views.edit_profile(user, request)
290        assert isinstance(response['form'], OWFormRenderer)
291        # error on the missing email field
292        error = u'Please enter an email address'
293        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
294        assert response['form'].errorlist() == html_error
295        assert response['form'].errors_for('email') == [error]
296
297    def test_edit_profile_post_ok_picture_empty_bytes(
298            self, profile_post_request, john):
299        """
300        POST request with an empty picture, the content of
301        request['POST'].picture is a empty bytes string (b'') which triggers
302        a bug in formencode, we put a fix in place, test that
303        (more in ow.user.views.edit_profile)
304        """
305        # for the purposes of this test, we can mock the picture
306        picture = Mock()
307        john.picture = picture
308        request = profile_post_request
309        user = john
310        # Mimic what happens when a picture is not provided by the user
311        request.POST['picture'] = b''
312        response = user_views.edit_profile(user, request)
313        assert isinstance(response, HTTPFound)
314        assert response.location == request.resource_url(user, 'profile')
315        assert user.picture == picture
316
317    def test_edit_profile_post_ok_missing_picture(
318            self, profile_post_request, john):
319        """
320        POST request without picture
321        """
322        # for the purposes of this test, we can mock the picture
323        picture = Mock()
324        john.picture = picture
325        request = profile_post_request
326        user = john
327        # No pic is provided in the request POST values
328        del request.POST['picture']
329        response = user_views.edit_profile(user, request)
330        assert isinstance(response, HTTPFound)
331        assert response.location == request.resource_url(user, 'profile')
332        assert user.picture == picture
333
334    def test_edit_profile_post_ok_nickname(self, profile_post_request, john):
335        """
336        User with a nickname set saves profile without changing the profile,
337        we have to be sure there are no "nickname already in use" errors
338        """
339        request = profile_post_request
340        user = john
341        user.nickname = 'mr_jones'
342        # add the nickname, the default post request has not a nickname set
343        request.POST['nickname'] = 'mr_jones'
344        response = user_views.edit_profile(user, request)
345        assert isinstance(response, HTTPFound)
346        assert response.location == request.resource_url(user, 'profile')
347
348    def test_change_password_get(self, dummy_request, john):
349        request = dummy_request
350        user = john
351        response = user_views.change_password(user, request)
352        assert isinstance(response['form'], OWFormRenderer)
353        # no errors in the form (first load)
354        assert response['form'].errorlist() == ''
355
356    def test_change_password_post_ok(self, passwd_post_request, john):
357        request = passwd_post_request
358        user = john
359        request.POST['old_password'] = 's3cr3t'
360        request.POST['password'] = 'h1dd3n s3cr3t'
361        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
362        response = user_views.change_password(user, request)
363        assert isinstance(response, HTTPFound)
364        assert response.location == request.resource_url(user, 'profile')
365        # password was changed
366        assert not user.check_password('s3cr3t')
367        assert user.check_password('h1dd3n s3cr3t')
368
369    def test_change_password_post_no_values(self, passwd_post_request, john):
370        request = passwd_post_request
371        user = john
372        response = user_views.change_password(user, request)
373        assert isinstance(response['form'], OWFormRenderer)
374        error = u'Please enter a value'
375        html_error = u'<ul class="error">'
376        html_error += ('<li>' + error + '</li>') * 3  # 3 fields
377        html_error += '</ul>'
378        errorlist = response['form'].errorlist().replace('\n', '')
379        assert errorlist == html_error
380        assert response['form'].errors_for('old_password') == [error]
381        assert response['form'].errors_for('password') == [error]
382        assert response['form'].errors_for('password_confirm') == [error]
383        # password was not changed
384        assert user.check_password('s3cr3t')
385
386    def test_change_password_post_bad_old_password(
387            self, passwd_post_request, john):
388        request = passwd_post_request
389        user = john
390        request.POST['old_password'] = 'FAIL PASSWORD'
391        request.POST['password'] = 'h1dd3n s3cr3t'
392        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
393        response = user_views.change_password(user, request)
394        assert isinstance(response['form'], OWFormRenderer)
395        error = u'The given password does not match the existing one '
396        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
397        assert response['form'].errorlist() == html_error
398        assert response['form'].errors_for('old_password') == [error]
399        # password was not changed
400        assert user.check_password('s3cr3t')
401        assert not user.check_password('h1dd3n s3cr3t')
402
403    def test_change_password_post_password_mismatch(
404            self, passwd_post_request, john):
405        request = passwd_post_request
406        user = john
407        request.POST['old_password'] = 's3cr3t'
408        request.POST['password'] = 'h1dd3n s3cr3ts'
409        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
410        response = user_views.change_password(user, request)
411        assert isinstance(response['form'], OWFormRenderer)
412        error = u'Fields do not match'
413        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
414        assert response['form'].errorlist() == html_error
415        assert response['form'].errors_for('password_confirm') == [error]
416        # password was not changed
417        assert user.check_password('s3cr3t')
418        assert not user.check_password('h1dd3n s3cr3t')
419
420    def test_signup_get(self, dummy_request):
421        request = dummy_request
422        response = user_views.signup(request.root, request)
423        assert isinstance(response['form'], OWFormRenderer)
424        # no errors in the form (first load)
425        assert response['form'].errorlist() == ''
426
427    def test_signup_post_ok(self, signup_post_request):
428        request = signup_post_request
429        assert 'jack.black@example.net' not in request.root.emails
430        assert 'JackBlack' not in request.root.all_nicknames
431        response = user_views.signup(request.root, request)
432        assert isinstance(response, HTTPFound)
433        assert response.location == request.resource_url(request.root)
434        assert 'jack.black@example.net' in request.root.emails
435        assert 'JackBlack' in request.root.all_nicknames
436
437    def test_signup_missing_required(self, signup_post_request):
438        request = signup_post_request
439        request.POST['email'] = ''
440        assert 'jack.black@example.net' not in request.root.emails
441        assert 'JackBlack' not in request.root.all_nicknames
442        response = user_views.signup(request.root, request)
443        assert isinstance(response['form'], OWFormRenderer)
444        error = u'Please enter an email address'
445        html_error = '<ul class="error">'
446        html_error += '<li>' + error + '</li>'
447        html_error += '</ul>'
448        errorlist = response['form'].errorlist().replace('\n', '')
449        assert errorlist == html_error
450        assert response['form'].errors_for('email') == [error]
451        assert 'jack.black@example.net' not in request.root.emails
452        assert 'JackBlack' not in request.root.all_nicknames
453
454    def test_signup_existing_nickname(self, signup_post_request, john):
455        request = signup_post_request
456        # assign john a nickname first
457        john.nickname = 'john'
458        # now set it for the POST request
459        request.POST['nickname'] = 'john'
460        # check jack is not there yet
461        assert 'jack.black@example.net' not in request.root.emails
462        assert 'JackBlack' not in request.root.all_nicknames
463        # now signup as jack, but trying to set the nickname 'john'
464        response = user_views.signup(request.root, request)
465        assert isinstance(response['form'], OWFormRenderer)
466        error = u'Another user is already using the nickname john'
467        html_error = '<ul class="error">'
468        html_error += '<li>' + error + '</li>'
469        html_error += '</ul>'
470        errorlist = response['form'].errorlist().replace('\n', '')
471        assert errorlist == html_error
472        assert response['form'].errors_for('nickname') == [error]
473        # all the errors, and jack is not there
474        assert 'jack.black@example.net' not in request.root.emails
475        assert 'JackBlack' not in request.root.all_nicknames
476
477    def test_signup_existing_email(self, signup_post_request):
478        request = signup_post_request
479        request.POST['email'] = 'john.doe@example.net'
480        assert 'jack.black@example.net' not in request.root.emails
481        assert 'JackBlack' not in request.root.all_nicknames
482        response = user_views.signup(request.root, request)
483        assert isinstance(response['form'], OWFormRenderer)
484        error = u'Another user is already registered with the email '
485        error += u'john.doe@example.net'
486        html_error = '<ul class="error">'
487        html_error += '<li>' + error + '</li>'
488        html_error += '</ul>'
489        errorlist = response['form'].errorlist().replace('\n', '')
490        assert errorlist == html_error
491        assert response['form'].errors_for('email') == [error]
492        assert 'jack.black@example.net' not in request.root.emails
493        assert 'JackBlack' not in request.root.all_nicknames
Note: See TracBrowser for help on using the repository browser.