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

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

(#23) - Show workouts in the dashboard with date filtering

  • Property mode set to 100644
File size: 23.5 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 len(response) == 4
155        assert 'month_name' in response.keys()
156        # this user has a single workout, in 2015
157        assert response['viewing_year'] == 2015
158        assert response['viewing_month'] == 6
159        assert response['workouts'] == [w for w in john.workouts()]
160
161    def test_dashboard_year(self, dummy_request, john):
162        """
163        Renders the user dashboard for a chosen year.
164        """
165        request = dummy_request
166        # first test the year for which we know there is a workout
167        request.GET['year'] = 2015
168        response = user_views.dashboard(john, request)
169        assert len(response) == 4
170        assert 'month_name' in response.keys()
171        # this user has a single workout, in 2015
172        assert response['viewing_year'] == 2015
173        assert response['viewing_month'] == 6
174        assert response['workouts'] == [w for w in john.workouts()]
175        # now, a year we know there is no workout info
176        request.GET['year'] = 2000
177        response = user_views.dashboard(john, request)
178        assert len(response) == 4
179        assert 'month_name' in response.keys()
180        # this user has a single workout, in 2015
181        assert response['viewing_year'] == 2000
182        # we have no data for that year and we didn't ask for a certain month,
183        # so the passing value for that is None
184        assert response['viewing_month'] is None
185        assert response['workouts'] == []
186
187    def test_dashboard_year_month(self, dummy_request, john):
188        """
189        Renders the user dashboard for a chosen year and month.
190        """
191        request = dummy_request
192        # first test the year/month for which we know there is a workout
193        request.GET['year'] = 2015
194        request.GET['month'] = 6
195        response = user_views.dashboard(john, request)
196        assert len(response) == 4
197        assert 'month_name' in response.keys()
198        # this user has a single workout, in 2015
199        assert response['viewing_year'] == 2015
200        assert response['viewing_month'] == 6
201        assert response['workouts'] == [w for w in john.workouts()]
202        # now, change month to one without values
203        request.GET['month'] = 2
204        response = user_views.dashboard(john, request)
205        assert len(response) == 4
206        assert 'month_name' in response.keys()
207        # this user has a single workout, in 2015
208        assert response['viewing_year'] == 2015
209        assert response['viewing_month'] == 2
210        assert response['workouts'] == []
211        # now the month with data, but in a different year
212        request.GET['year'] = 2010
213        request.GET['month'] = 6
214        response = user_views.dashboard(john, request)
215        assert len(response) == 4
216        assert 'month_name' in response.keys()
217        # this user has a single workout, in 2015
218        assert response['viewing_year'] == 2010
219        assert response['viewing_month'] == 6
220        assert response['workouts'] == []
221
222    def test_dashboard_month(self, dummy_request, john):
223        """
224        Passing a month without a year when rendering the dashboard. The last
225        year for which workout data is available is assumed
226        """
227        request = dummy_request
228        # Set a month without workout data
229        request.GET['month'] = 5
230        response = user_views.dashboard(john, request)
231        assert len(response) == 4
232        assert 'month_name' in response.keys()
233        # this user has a single workout, in 2015
234        assert response['viewing_year'] == 2015
235        assert response['viewing_month'] == 5
236        assert response['workouts'] == []
237        # now a month with data
238        request.GET['month'] = 6
239        response = user_views.dashboard(john, request)
240        assert len(response) == 4
241        assert 'month_name' in response.keys()
242        # this user has a single workout, in 2015
243        assert response['viewing_year'] == 2015
244        assert response['viewing_month'] == 6
245        assert response['workouts'] == [w for w in john.workouts()]
246
247    def test_profile(self, dummy_request, john):
248        """
249        Renders the user profile page
250        """
251        request = dummy_request
252        response = user_views.profile(john, request)
253        assert response == {}
254
255    def test_login_get(self, dummy_request):
256        """
257        GET request to access the login page
258        """
259        request = dummy_request
260        response = user_views.login(request.root, request)
261        assert response['message'] == ''
262        assert response['email'] == ''
263        assert response['password'] == ''
264        assert response['redirect_url'] == request.resource_url(request.root)
265
266    def test_login_get_return_to(self, dummy_request, john):
267        """
268        GET request to access the login page, if there is a page set to where
269        the user should be sent to, the response "redirect_url" key will have
270        such url
271        """
272        request = dummy_request
273        workout = john.workouts()[0]
274        workout_url = request.resource_url(workout)
275        request.params['return_to'] = workout_url
276        response = user_views.login(request.root, request)
277        assert response['redirect_url'] == workout_url
278
279    def test_login_post_wrong_email(self, dummy_request):
280        request = dummy_request
281        request.method = 'POST'
282        request.POST['submit'] = True
283        request.POST['email'] = 'jack@example.net'
284        response = user_views.login(request.root, request)
285        assert response['message'] == u'Wrong email address'
286
287    def test_login_post_wrong_password(self, dummy_request):
288        request = dummy_request
289        request.method = 'POST'
290        request.POST['submit'] = True
291        request.POST['email'] = 'john.doe@example.net'
292        request.POST['password'] = 'badpassword'
293        response = user_views.login(request.root, request)
294        assert response['message'] == u'Wrong password'
295
296    @patch('ow.views.user.remember')
297    def test_login_post_ok(self, rem, dummy_request, john):
298        request = dummy_request
299        request.method = 'POST'
300        request.POST['submit'] = True
301        request.POST['email'] = 'john.doe@example.net'
302        request.POST['password'] = 's3cr3t'
303        response = user_views.login(request.root, request)
304        assert isinstance(response, HTTPFound)
305        assert rem.called
306        assert response.location == request.resource_url(john)
307
308    @patch('ow.views.user.forget')
309    def test_logout(self, forg, dummy_request):
310        request = dummy_request
311        response = user_views.logout(request.root, request)
312        assert isinstance(response, HTTPFound)
313        assert forg.called
314        assert response.location == request.resource_url(request.root)
315
316    extensions = ('png', 'jpg', 'jpeg', 'gif')
317
318    @pytest.mark.parametrize('extension', extensions)
319    def test_profile_picture(self, extension, dummy_request, john):
320        """
321        GET request to get the profile picture of an user.
322        """
323        request = dummy_request
324        # Get the user
325        user = john
326        # Get the path to the image, then open it and copy it to a new Blob
327        # object
328        path = 'fixtures/image.' + extension
329        image_path = os.path.join(
330            os.path.dirname(os.path.dirname(__file__)), path)
331        blob = Blob()
332        with open(image_path, 'rb') as infile, blob.open('w') as out:
333            infile.seek(0)
334            copyfileobj(infile, out)
335
336        # Set the blob with the picture
337        user.picture = blob
338
339        # Call the profile_picture view
340        response = user_views.profile_picture(user, request)
341        assert isinstance(response, Response)
342        assert response.status_int == 200
343        assert response.content_type == 'image'
344
345    def test_edit_profile_get(self, dummy_request, john):
346        """
347        GET request to the edit profile page, returns the form ready to
348        be rendered
349        """
350        request = dummy_request
351        user = john
352        response = user_views.edit_profile(user, request)
353        assert isinstance(response['form'], OWFormRenderer)
354        # no errors in the form (first load)
355        assert response['form'].errorlist() == ''
356        # the form carries along the proper data keys, taken from the
357        # loaded user profile
358        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
359                'birth_date', 'height', 'weight', 'gender', 'timezone']
360        assert list(response['form'].data.keys()) == data
361        # and check the email to see data is properly loaded
362        assert response['form'].data['email'] == 'john.doe@example.net'
363
364    def test_edit_profile_post_ok(self, profile_post_request, john):
365        request = profile_post_request
366        user = john
367        # Update the bio field
368        bio = 'Some text about this user'
369        request.POST['bio'] = bio
370        response = user_views.edit_profile(user, request)
371        assert isinstance(response, HTTPFound)
372        assert response.location == request.resource_url(user, 'profile')
373        assert user.bio == bio
374
375    def test_edit_profile_post_missing_required(
376            self, profile_post_request, john):
377        request = profile_post_request
378        request.POST['email'] = ''
379        user = john
380        response = user_views.edit_profile(user, request)
381        assert isinstance(response['form'], OWFormRenderer)
382        # error on the missing email field
383        error = u'Please enter an email address'
384        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
385        assert response['form'].errorlist() == html_error
386        assert response['form'].errors_for('email') == [error]
387
388    def test_edit_profile_post_ok_picture_empty_bytes(
389            self, profile_post_request, john):
390        """
391        POST request with an empty picture, the content of
392        request['POST'].picture is a empty bytes string (b'') which triggers
393        a bug in formencode, we put a fix in place, test that
394        (more in ow.user.views.edit_profile)
395        """
396        # for the purposes of this test, we can mock the picture
397        picture = Mock()
398        john.picture = picture
399        request = profile_post_request
400        user = john
401        # Mimic what happens when a picture is not provided by the user
402        request.POST['picture'] = b''
403        response = user_views.edit_profile(user, request)
404        assert isinstance(response, HTTPFound)
405        assert response.location == request.resource_url(user, 'profile')
406        assert user.picture == picture
407
408    def test_edit_profile_post_ok_missing_picture(
409            self, profile_post_request, john):
410        """
411        POST request without picture
412        """
413        # for the purposes of this test, we can mock the picture
414        picture = Mock()
415        john.picture = picture
416        request = profile_post_request
417        user = john
418        # No pic is provided in the request POST values
419        del request.POST['picture']
420        response = user_views.edit_profile(user, request)
421        assert isinstance(response, HTTPFound)
422        assert response.location == request.resource_url(user, 'profile')
423        assert user.picture == picture
424
425    def test_edit_profile_post_ok_nickname(self, profile_post_request, john):
426        """
427        User with a nickname set saves profile without changing the profile,
428        we have to be sure there are no "nickname already in use" errors
429        """
430        request = profile_post_request
431        user = john
432        user.nickname = 'mr_jones'
433        # add the nickname, the default post request has not a nickname set
434        request.POST['nickname'] = 'mr_jones'
435        response = user_views.edit_profile(user, request)
436        assert isinstance(response, HTTPFound)
437        assert response.location == request.resource_url(user, 'profile')
438
439    def test_change_password_get(self, dummy_request, john):
440        request = dummy_request
441        user = john
442        response = user_views.change_password(user, request)
443        assert isinstance(response['form'], OWFormRenderer)
444        # no errors in the form (first load)
445        assert response['form'].errorlist() == ''
446
447    def test_change_password_post_ok(self, passwd_post_request, john):
448        request = passwd_post_request
449        user = john
450        request.POST['old_password'] = 's3cr3t'
451        request.POST['password'] = 'h1dd3n s3cr3t'
452        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
453        response = user_views.change_password(user, request)
454        assert isinstance(response, HTTPFound)
455        assert response.location == request.resource_url(user, 'profile')
456        # password was changed
457        assert not user.check_password('s3cr3t')
458        assert user.check_password('h1dd3n s3cr3t')
459
460    def test_change_password_post_no_values(self, passwd_post_request, john):
461        request = passwd_post_request
462        user = john
463        response = user_views.change_password(user, request)
464        assert isinstance(response['form'], OWFormRenderer)
465        error = u'Please enter a value'
466        html_error = u'<ul class="error">'
467        html_error += ('<li>' + error + '</li>') * 3  # 3 fields
468        html_error += '</ul>'
469        errorlist = response['form'].errorlist().replace('\n', '')
470        assert errorlist == html_error
471        assert response['form'].errors_for('old_password') == [error]
472        assert response['form'].errors_for('password') == [error]
473        assert response['form'].errors_for('password_confirm') == [error]
474        # password was not changed
475        assert user.check_password('s3cr3t')
476
477    def test_change_password_post_bad_old_password(
478            self, passwd_post_request, john):
479        request = passwd_post_request
480        user = john
481        request.POST['old_password'] = 'FAIL PASSWORD'
482        request.POST['password'] = 'h1dd3n s3cr3t'
483        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
484        response = user_views.change_password(user, request)
485        assert isinstance(response['form'], OWFormRenderer)
486        error = u'The given password does not match the existing one '
487        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
488        assert response['form'].errorlist() == html_error
489        assert response['form'].errors_for('old_password') == [error]
490        # password was not changed
491        assert user.check_password('s3cr3t')
492        assert not user.check_password('h1dd3n s3cr3t')
493
494    def test_change_password_post_password_mismatch(
495            self, passwd_post_request, john):
496        request = passwd_post_request
497        user = john
498        request.POST['old_password'] = 's3cr3t'
499        request.POST['password'] = 'h1dd3n s3cr3ts'
500        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
501        response = user_views.change_password(user, request)
502        assert isinstance(response['form'], OWFormRenderer)
503        error = u'Fields do not match'
504        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
505        assert response['form'].errorlist() == html_error
506        assert response['form'].errors_for('password_confirm') == [error]
507        # password was not changed
508        assert user.check_password('s3cr3t')
509        assert not user.check_password('h1dd3n s3cr3t')
510
511    def test_signup_get(self, dummy_request):
512        request = dummy_request
513        response = user_views.signup(request.root, request)
514        assert isinstance(response['form'], OWFormRenderer)
515        # no errors in the form (first load)
516        assert response['form'].errorlist() == ''
517
518    def test_signup_post_ok(self, signup_post_request):
519        request = signup_post_request
520        assert 'jack.black@example.net' not in request.root.emails
521        assert 'JackBlack' not in request.root.all_nicknames
522        response = user_views.signup(request.root, request)
523        assert isinstance(response, HTTPFound)
524        assert response.location == request.resource_url(request.root)
525        assert 'jack.black@example.net' in request.root.emails
526        assert 'JackBlack' in request.root.all_nicknames
527
528    def test_signup_missing_required(self, signup_post_request):
529        request = signup_post_request
530        request.POST['email'] = ''
531        assert 'jack.black@example.net' not in request.root.emails
532        assert 'JackBlack' not in request.root.all_nicknames
533        response = user_views.signup(request.root, request)
534        assert isinstance(response['form'], OWFormRenderer)
535        error = u'Please enter an email address'
536        html_error = '<ul class="error">'
537        html_error += '<li>' + error + '</li>'
538        html_error += '</ul>'
539        errorlist = response['form'].errorlist().replace('\n', '')
540        assert errorlist == html_error
541        assert response['form'].errors_for('email') == [error]
542        assert 'jack.black@example.net' not in request.root.emails
543        assert 'JackBlack' not in request.root.all_nicknames
544
545    def test_signup_existing_nickname(self, signup_post_request, john):
546        request = signup_post_request
547        # assign john a nickname first
548        john.nickname = 'john'
549        # now set it for the POST request
550        request.POST['nickname'] = 'john'
551        # check jack is not there yet
552        assert 'jack.black@example.net' not in request.root.emails
553        assert 'JackBlack' not in request.root.all_nicknames
554        # now signup as jack, but trying to set the nickname 'john'
555        response = user_views.signup(request.root, request)
556        assert isinstance(response['form'], OWFormRenderer)
557        error = u'Another user is already using the nickname john'
558        html_error = '<ul class="error">'
559        html_error += '<li>' + error + '</li>'
560        html_error += '</ul>'
561        errorlist = response['form'].errorlist().replace('\n', '')
562        assert errorlist == html_error
563        assert response['form'].errors_for('nickname') == [error]
564        # all the errors, and jack is not there
565        assert 'jack.black@example.net' not in request.root.emails
566        assert 'JackBlack' not in request.root.all_nicknames
567
568    def test_signup_existing_email(self, signup_post_request):
569        request = signup_post_request
570        request.POST['email'] = 'john.doe@example.net'
571        assert 'jack.black@example.net' not in request.root.emails
572        assert 'JackBlack' not in request.root.all_nicknames
573        response = user_views.signup(request.root, request)
574        assert isinstance(response['form'], OWFormRenderer)
575        error = u'Another user is already registered with the email '
576        error += u'john.doe@example.net'
577        html_error = '<ul class="error">'
578        html_error += '<li>' + error + '</li>'
579        html_error += '</ul>'
580        errorlist = response['form'].errorlist().replace('\n', '')
581        assert errorlist == html_error
582        assert response['form'].errors_for('email') == [error]
583        assert 'jack.black@example.net' not in request.root.emails
584        assert 'JackBlack' not in request.root.all_nicknames
Note: See TracBrowser for help on using the repository browser.