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

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

(#56) Add support for different locale/language:

  • Let users choose their lang/locale in the edit profile page
  • Set the currently selected locale as a cookie (following pyramid docs on how to set the locale using the default locale negotiator)
  • Save the locale setting for each user as an attribute on the User model
  • Set the proper locale as a cookie on login
  • Unset the locale cookie on logout

Default available locales for now are en (english) and es (spanish)

  • Property mode set to 100644
File size: 41.5 KB
Line 
1import os
2import json
3from decimal import Decimal
4from datetime import datetime, timedelta, timezone
5from shutil import copyfileobj
6from unittest.mock import Mock, patch
7from io import BytesIO
8from uuid import UUID
9
10import pytest
11
12from ZODB.blob import Blob
13
14from pyramid.testing import DummyRequest
15from pyramid.httpexceptions import HTTPFound
16from pyramid.response import Response
17
18from webob.multidict import MultiDict
19
20from PIL import Image
21
22from pytz import common_timezones
23
24from ow.models.root import OpenWorkouts
25from ow.models.user import User
26from ow.models.workout import Workout
27from ow.views.renderers import OWFormRenderer
28from ow.utilities import get_available_locale_names
29import ow.views.user as user_views
30
31
32class TestUserViews(object):
33
34    @pytest.fixture
35    def john(self):
36        user = User(firstname='John', lastname='Doe',
37                    email='john.doe@example.net')
38        user.password = 's3cr3t'
39        return user
40
41    @pytest.fixture
42    def root(self, john):
43        root = OpenWorkouts()
44        root.add_user(john)
45        workout = Workout(
46            start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc),
47            duration=timedelta(minutes=60),
48            distance=30
49        )
50        john.add_workout(workout)
51        return root
52
53    @pytest.fixture
54    def dummy_request(self, root):
55        request = DummyRequest()
56        request.registry.settings = {
57            'pyramid.default_locale_name': 'en'
58        }
59        request.root = root
60        return request
61
62    @pytest.fixture
63    def profile_post_request(self, root, john):
64        """
65        This is a valid POST request to update an user profile.
66        Form will validate, but nothing will be really updated/changed.
67        """
68        user = john
69        request = DummyRequest()
70        request.root = root
71        request.method = 'POST'
72        request.POST = MultiDict({
73            'submit': True,
74            'firstname': user.firstname,
75            'lastname': user.lastname,
76            'email': user.email,
77            'bio': user.bio,
78            'weight': user.weight,
79            'height': user.height,
80            'gender': user.gender,
81            'birth_date': user.birth_date,
82            'picture': user.picture,
83            })
84        return request
85
86    @pytest.fixture
87    def passwd_post_request(self, root):
88        """
89        This is a valid POST request to change the user password, but
90        the form will not validate (empty fields)
91        """
92        request = DummyRequest()
93        request.root = root
94        request.method = 'POST'
95        request.POST = MultiDict({
96            'submit': True,
97            'old_password': '',
98            'password': '',
99            'password_confirm': ''
100            })
101        return request
102
103    @pytest.fixture
104    def signup_post_request(self, root):
105        """
106        This is a valid POST request to signup a new user.
107        """
108        request = DummyRequest()
109        request.root = root
110        request.method = 'POST'
111        request.POST = MultiDict({
112            'submit': True,
113            'nickname': 'JackBlack',
114            'email': 'jack.black@example.net',
115            'firstname': 'Jack',
116            'lastname': 'Black',
117            'password': 'j4ck s3cr3t',
118            'password_confirm': 'j4ck s3cr3t'
119            })
120        return request
121
122    @patch('ow.views.user.remember')
123    def test_verify_already_verified(self, remember, dummy_request, john):
124        john.verified = True
125        response = user_views.verify(john, dummy_request)
126        assert isinstance(response, HTTPFound)
127        assert response.location == dummy_request.resource_url(john)
128        # user was not authenticated
129        assert not remember.called
130        # verified status did not change
131        assert john.verified
132
133    @patch('ow.views.user.remember')
134    def test_verify_no_subpath(self, remember, dummy_request, john):
135        response = user_views.verify(john, dummy_request)
136        # the verify info page is rendered, we don't pass anything to the
137        # rendering context
138        assert response == {}
139        # user was not authenticated
140        assert not remember.called
141        # verified status did not change
142        assert not john.verified
143
144    def test_verify_subpath_not_verified(self, dummy_request, john):
145        dummy_request.subpath = ['not_the_token']
146        response = user_views.verify(john, dummy_request)
147        # the verify info page is rendered, we don't pass anything to the
148        # rendering context
149        assert response == {}
150
151    @patch('ow.views.user.remember')
152    def test_verify_wrong_token(self, remember, dummy_request, john):
153        token = 'some-uuid4'
154        john.verification_token = 'some-other-uuid4'
155        dummy_request.subpath = [token]
156        response = user_views.verify(john, dummy_request)
157        # the verify info page is rendered, we don't pass anything to the
158        # rendering context
159        assert response == {}
160        # user was not authenticated
161        assert not remember.called
162        # verified status did not change, neither did the token
163        assert not john.verified
164        assert john.verification_token == 'some-other-uuid4'
165
166    @patch('ow.views.user.remember')
167    def test_verify_verifying(self, remember, dummy_request, john):
168        token = 'some-uuid4'
169        john.verification_token = token
170        dummy_request.subpath = [token]
171        response = user_views.verify(john, dummy_request)
172        # redirect to user page
173        assert isinstance(response, HTTPFound)
174        assert response.location == dummy_request.resource_url(john)
175        # user was authenticated after verified
176        remember.assert_called_with(dummy_request, str(john.uid))
177        # user has been verified
178        assert john.verified
179
180    @patch('ow.views.user.send_verification_email')
181    def test_resend_verification_already_verified(
182            self, sve, dummy_request, john):
183        john.verified = True
184        assert john.verification_token is None
185        response = user_views.resend_verification_link(john, dummy_request)
186        assert isinstance(response, HTTPFound)
187        assert response.location == dummy_request.resource_url(
188            dummy_request.root, 'login', query={'message': 'already-verified'}
189        )
190        # no emails have been sent
191        assert not sve.called
192        # the token has not been modified
193        assert john.verification_token is None
194
195    @patch('ow.views.user.send_verification_email')
196    def test_resend_verification(self, sve, dummy_request, john):
197        john.verified = False
198        john.verification_token = 'faked-uuid4-verification-token'
199        assert john.verification_tokens_sent == 0
200        response = user_views.resend_verification_link(john, dummy_request)
201        assert isinstance(response, HTTPFound)
202        assert response.location == dummy_request.resource_url(
203            dummy_request.root, 'login',
204            query={'message': 'link-sent', 'email': john.email}
205        )
206        # no emails have been sent
207        sve.assert_called_once_with(dummy_request, john)
208        # the token has not been modified
209        assert john.verification_token == 'faked-uuid4-verification-token'
210        # and we have registered that we sent a token
211        assert john.verification_tokens_sent == 1
212
213    @patch('ow.views.user.send_verification_email')
214    def test_resend_verification_new_token(self, sve, dummy_request, john):
215        john.verified = False
216        john.verification_token = None
217        assert john.verification_tokens_sent == 0
218        response = user_views.resend_verification_link(john, dummy_request)
219        assert isinstance(response, HTTPFound)
220        assert response.location == dummy_request.resource_url(
221            dummy_request.root, 'login',
222            query={'message': 'link-sent', 'email': john.email}
223        )
224        # no emails have been sent
225        sve.assert_called_once_with(dummy_request, john)
226        # the token has been modified
227        assert john.verification_token is not None
228        assert john.verification_tokens_sent == 1
229
230    @patch('ow.views.user.send_verification_email')
231    def test_resend_verification_limit(
232            self, sve, dummy_request, john):
233        john.verified = False
234        john.verification_token = 'faked-uuid4-verification-token'
235        john.verification_tokens_sent = 4
236        response = user_views.resend_verification_link(john, dummy_request)
237        assert isinstance(response, HTTPFound)
238        assert response.location == dummy_request.resource_url(
239            dummy_request.root, 'login',
240            query={'message': 'max-tokens-sent', 'email': john.email}
241        )
242        # no emails have been sent
243        assert not sve.called
244        # the token has not been modified
245        assert john.verification_token == 'faked-uuid4-verification-token'
246        assert john.verification_tokens_sent == 4
247
248    def test_dashboard_redirect_unauthenticated(self, root):
249        """
250        Anonymous access to the root object, send the user to the login page.
251
252        Instead of reusing the DummyRequest from the request fixture, we do
253        Mock fully the request here, because we need to use
254        authenticated_userid, which cannot be easily set in the DummyRequest
255        """
256        request = DummyRequest()
257        request.root = root
258        response = user_views.dashboard_redirect(root, request)
259        assert isinstance(response, HTTPFound)
260        assert response.location == request.resource_url(root, 'login')
261
262    def test_dashboard_redirect_authenticated(self, root, john):
263        """
264        Authenticated user accesing the root object, send the user to her
265        dashboard
266
267        Instead of reusing the DummyRequest from the request fixture, we do
268        Mock fully the request here, because we need to use
269        authenticated_userid, which cannot be easily set in the DummyRequest
270        """
271        alt_request = DummyRequest()
272        request = Mock()
273        request.root = root
274        request.authenticated_userid = str(john.uid)
275        request.resource_url = alt_request.resource_url
276        response = user_views.dashboard_redirect(root, request)
277        assert isinstance(response, HTTPFound)
278        assert response.location == request.resource_url(john)
279        # if authenticated_userid is the id of an user that does not exist
280        # anymore, we send the user to the logout page
281        request.authenticated_userid = 'faked-uid'
282        response = user_views.dashboard_redirect(root, request)
283        assert isinstance(response, HTTPFound)
284        assert response.location == request.resource_url(root, 'logout')
285
286    def test_dashboard(self, dummy_request, john):
287        """
288        Renders the user dashboard
289        """
290        request = dummy_request
291        response = user_views.dashboard(john, request)
292        assert len(response) == 6
293        assert 'month_name' in response.keys()
294        assert response['current_year'] == datetime.now().year
295        assert response['current_day_name'] == datetime.now().strftime('%a')
296        # this user has a single workout, in 2015
297        assert response['viewing_year'] == 2015
298        assert response['viewing_month'] == 6
299        assert response['workouts'] == [w for w in john.workouts()]
300
301    def test_dashboard_year(self, dummy_request, john):
302        """
303        Renders the user dashboard for a chosen year.
304        """
305        request = dummy_request
306        # first test the year for which we know there is a workout
307        request.GET['year'] = 2015
308        response = user_views.dashboard(john, request)
309        assert len(response) == 6
310        assert 'month_name' in response.keys()
311        assert response['current_year'] == datetime.now().year
312        assert response['current_day_name'] == datetime.now().strftime('%a')
313        # this user has a single workout, in 2015
314        assert response['viewing_year'] == 2015
315        assert response['viewing_month'] == 6
316        assert response['workouts'] == [w for w in john.workouts()]
317        # now, a year we know there is no workout info
318        request.GET['year'] = 2000
319        response = user_views.dashboard(john, request)
320        assert len(response) == 6
321        assert 'month_name' in response.keys()
322        assert response['current_year'] == datetime.now().year
323        assert response['current_day_name'] == datetime.now().strftime('%a')
324        # this user has a single workout, in 2015
325        assert response['viewing_year'] == 2000
326        # we have no data for that year and we didn't ask for a certain month,
327        # so the passing value for that is None
328        assert response['viewing_month'] is None
329        assert response['workouts'] == []
330
331    def test_dashboard_year_month(self, dummy_request, john):
332        """
333        Renders the user dashboard for a chosen year and month.
334        """
335        request = dummy_request
336        # first test the year/month for which we know there is a workout
337        request.GET['year'] = 2015
338        request.GET['month'] = 6
339        response = user_views.dashboard(john, request)
340        assert len(response) == 6
341        assert 'month_name' in response.keys()
342        assert response['current_year'] == datetime.now().year
343        assert response['current_day_name'] == datetime.now().strftime('%a')
344        # this user has a single workout, in 2015
345        assert response['viewing_year'] == 2015
346        assert response['viewing_month'] == 6
347        assert response['workouts'] == [w for w in john.workouts()]
348        # now, change month to one without values
349        request.GET['month'] = 2
350        response = user_views.dashboard(john, request)
351        assert len(response) == 6
352        assert 'month_name' in response.keys()
353        assert response['current_year'] == datetime.now().year
354        assert response['current_day_name'] == datetime.now().strftime('%a')
355        # this user has a single workout, in 2015
356        assert response['viewing_year'] == 2015
357        assert response['viewing_month'] == 2
358        assert response['workouts'] == []
359        # now the month with data, but in a different year
360        request.GET['year'] = 2010
361        request.GET['month'] = 6
362        response = user_views.dashboard(john, request)
363        assert len(response) == 6
364        assert 'month_name' in response.keys()
365        assert response['current_year'] == datetime.now().year
366        assert response['current_day_name'] == datetime.now().strftime('%a')
367        # this user has a single workout, in 2015
368        assert response['viewing_year'] == 2010
369        assert response['viewing_month'] == 6
370        assert response['workouts'] == []
371
372    def test_dashboard_month(self, dummy_request, john):
373        """
374        Passing a month without a year when rendering the dashboard. The last
375        year for which workout data is available is assumed
376        """
377        request = dummy_request
378        # Set a month without workout data
379        request.GET['month'] = 5
380        response = user_views.dashboard(john, request)
381        assert len(response) == 6
382        assert 'month_name' in response.keys()
383        assert response['current_year'] == datetime.now().year
384        assert response['current_day_name'] == datetime.now().strftime('%a')
385        # this user has a single workout, in 2015
386        assert response['viewing_year'] == 2015
387        assert response['viewing_month'] == 5
388        assert response['workouts'] == []
389        # now a month with data
390        request.GET['month'] = 6
391        response = user_views.dashboard(john, request)
392        assert len(response) == 6
393        assert 'month_name' in response.keys()
394        assert response['current_year'] == datetime.now().year
395        assert response['current_day_name'] == datetime.now().strftime('%a')
396        # this user has a single workout, in 2015
397        assert response['viewing_year'] == 2015
398        assert response['viewing_month'] == 6
399        assert response['workouts'] == [w for w in john.workouts()]
400
401    def test_profile(self, dummy_request, john):
402        """
403        Renders the user profile page
404        """
405        request = dummy_request
406        # profile page for the current day (no workouts avalable)
407        response = user_views.profile(john, request)
408        assert len(response.keys()) == 5
409        current_month = datetime.now(timezone.utc).strftime('%Y-%m')
410        assert response['user'] == john
411        assert response['current_month'] == current_month
412        assert response['current_week'] is None
413        assert response['workouts'] == []
414        assert response['totals'] == {
415            'distance': Decimal(0),
416            'time': timedelta(0),
417            'elevation': Decimal(0)
418        }
419        # profile page for a previous date, that has workouts
420        request.GET['year'] = 2015
421        request.GET['month'] = 6
422        response = user_views.profile(john, request)
423        assert len(response.keys()) == 5
424        assert response['user'] == john
425        assert response['current_month'] == '2015-06'
426        assert response['current_week'] is None
427        workouts = john.workouts(2015, 6)
428        assert response['workouts'] == workouts
429        assert response['totals'] == {
430            'distance': workouts[0].distance,
431            'time': workouts[0].duration,
432            'elevation': Decimal(0)
433        }
434        # same, passing a week, first on a week without workouts
435        request.GET['year'] = 2015
436        request.GET['month'] = 6
437        request.GET['week'] = 25
438        response = user_views.profile(john, request)
439        assert len(response.keys()) == 5
440        assert response['user'] == john
441        assert response['current_month'] == '2015-06'
442        assert response['current_week'] == 25
443        assert response['workouts'] == []
444        assert response['totals'] == {
445            'distance': Decimal(0),
446            'time': timedelta(0),
447            'elevation': Decimal(0)
448        }
449        # now in a week with workouts
450        request.GET['year'] = 2015
451        request.GET['month'] = 6
452        request.GET['week'] = 26
453        response = user_views.profile(john, request)
454        assert len(response.keys()) == 5
455        assert response['user'] == john
456        assert response['current_month'] == '2015-06'
457        assert response['current_week'] == 26
458        workouts = john.workouts(2015, 6)
459        assert response['workouts'] == workouts
460        assert response['totals'] == {
461            'distance': workouts[0].distance,
462            'time': workouts[0].duration,
463            'elevation': Decimal(0)
464        }
465
466    def test_login_get(self, dummy_request):
467        """
468        GET request to access the login page
469        """
470        request = dummy_request
471        response = user_views.login(request.root, request)
472        assert response['message'] == ''
473        assert response['email'] == ''
474        assert response['password'] == ''
475        assert response['redirect_url'] == request.resource_url(request.root)
476        assert response['resend_verify_link'] is None
477
478    def test_login_get_return_to(self, dummy_request, john):
479        """
480        GET request to access the login page, if there is a page set to where
481        the user should be sent to, the response "redirect_url" key will have
482        such url
483        """
484        request = dummy_request
485        workout = john.workouts()[0]
486        workout_url = request.resource_url(workout)
487        request.params['return_to'] = workout_url
488        response = user_views.login(request.root, request)
489        assert response['redirect_url'] == workout_url
490
491    def test_login_get_GET_messages(self, dummy_request):
492        """
493        GET request to access the login page, passing as a GET parameter
494        a "message key" so a given message is shown to the user
495        """
496        request = dummy_request
497        # first try with the keys we know there are messages associated to
498        request.GET['message'] = 'already-verified'
499        response = user_views.login(request.root, request)
500        assert response['message'] == 'User has been verified already'
501        request.GET['message'] = 'link-sent'
502        response = user_views.login(request.root, request)
503        assert response['message'] == (
504            'Verification link sent, please check your inbox')
505        request.GET['message'] = 'max-tokens-sent'
506        response = user_views.login(request.root, request)
507        assert response['message'] == (
508            'We already sent you the verification link more than three times')
509
510        # now try with a key that has no message assigned
511        request.GET['message'] = 'invalid-message-key'
512        response = user_views.login(request.root, request)
513        assert response['message'] == ''
514
515    def test_login_get_GET_email(self, dummy_request):
516        """
517        GET request to access the login page, passing as a GET parameter
518        an email address. That email is used to automatically fill in the
519        email input in the login form.
520        """
521        request = dummy_request
522        # first try with the keys we know there are messages associated to
523        request.GET['email'] = 'user@example.net'
524        response = user_views.login(request.root, request)
525        assert response['email'] == 'user@example.net'
526
527    def test_login_post_wrong_email(self, dummy_request):
528        request = dummy_request
529        request.method = 'POST'
530        request.POST['submit'] = True
531        request.POST['email'] = 'jack@example.net'
532        response = user_views.login(request.root, request)
533        assert response['message'] == u'Wrong email address'
534
535    def test_login_post_wrong_password(self, dummy_request):
536        request = dummy_request
537        request.method = 'POST'
538        request.POST['submit'] = True
539        request.POST['email'] = 'john.doe@example.net'
540        request.POST['password'] = 'badpassword'
541        # verify the user first
542        request.root.users[0].verified = True
543        response = user_views.login(request.root, request)
544        assert response['message'] == u'Wrong password'
545
546    @patch('ow.views.user.remember')
547    def test_login_post_unverified(self, rem, dummy_request, john):
548        request = dummy_request
549        request.method = 'POST'
550        request.POST['submit'] = True
551        request.POST['email'] = 'john.doe@example.net'
552        request.POST['password'] = 's3cr3t'
553        response = user_views.login(request.root, request)
554        assert response['message'] == u'You have to verify your account first'
555
556    @patch('ow.views.user.remember')
557    def test_login_post_ok(self, rem, dummy_request, john):
558        request = dummy_request
559        request.method = 'POST'
560        request.POST['submit'] = True
561        request.POST['email'] = 'john.doe@example.net'
562        request.POST['password'] = 's3cr3t'
563        # verify the user first
564        john.verified = True
565        response = user_views.login(request.root, request)
566        assert isinstance(response, HTTPFound)
567        assert rem.called
568        assert response.location == request.resource_url(john)
569        # the response headers contain the proper set_cookie for the default
570        # locale
571        default_locale_name = request.registry.settings[
572            'pyramid.default_locale_name']
573        expected_locale_header = '_LOCALE_=' + default_locale_name + '; Path=/'
574        assert response.headers['Set-Cookie'] == expected_locale_header
575
576    @patch('ow.views.user.remember')
577    def test_login_post_ok_set_locale(self, rem, dummy_request, john):
578        # same as the previous test, but this time the user has set a
579        # locale different than the default one
580        request = dummy_request
581        request.method = 'POST'
582        request.POST['submit'] = True
583        request.POST['email'] = 'john.doe@example.net'
584        request.POST['password'] = 's3cr3t'
585        # verify the user first
586        john.verified = True
587        # set the locale
588        john.locale = 'es'
589        response = user_views.login(request.root, request)
590        assert isinstance(response, HTTPFound)
591        assert rem.called
592        assert response.location == request.resource_url(john)
593        # the response headers contain the proper set_cookie for the user
594        # locale setting
595        expected_locale_header = '_LOCALE_=es; Path=/'
596        assert response.headers['Set-Cookie'] == expected_locale_header
597
598    @patch('ow.views.user.forget')
599    def test_logout(self, forg, dummy_request):
600        request = dummy_request
601        response = user_views.logout(request.root, request)
602        assert isinstance(response, HTTPFound)
603        assert forg.called
604        assert response.location == request.resource_url(request.root)
605        # the response headers contain the needed Set-Cookie header that
606        # invalidates the _LOCALE_ cookie, preventing problems with users
607        # sharing the same web browser (one locale setting being set for
608        # another user)
609        expected_locale_header = '_LOCALE_=; Max-Age=0; Path=/; expires='
610        assert expected_locale_header in response.headers['Set-Cookie']
611
612    extensions = ('png', 'jpg', 'jpeg', 'gif')
613
614    @pytest.mark.parametrize('extension', extensions)
615    def test_profile_picture(self, extension, dummy_request, john):
616        """
617        GET request to get the profile picture of an user.
618        """
619        request = dummy_request
620        # Get the user
621        user = john
622        # Get the path to the image, then open it and copy it to a new Blob
623        # object
624        path = 'fixtures/image.' + extension
625        image_path = os.path.join(
626            os.path.dirname(os.path.dirname(__file__)), path)
627        blob = Blob()
628        with open(image_path, 'rb') as infile, blob.open('w') as out:
629            infile.seek(0)
630            copyfileobj(infile, out)
631
632        # Set the blob with the picture
633        user.picture = blob
634
635        # Call the profile_picture view
636        response = user_views.profile_picture(user, request)
637        assert isinstance(response, Response)
638        assert response.status_int == 200
639        assert response.content_type == 'image'
640        # as we did not pass a specific size as a get parameter, the size is
641        # the same as the original image
642        original_image = Image.open(image_path)
643        returned_image = Image.open(BytesIO(response.body))
644        assert original_image.size == returned_image.size
645
646        # now, ask for a smaller image
647        request.GET['size'] = original_image.size[0] - 20
648        response = user_views.profile_picture(user, request)
649        assert isinstance(response, Response)
650        assert response.status_int == 200
651        assert response.content_type == 'image'
652        # now the size of the original image is bigger
653        returned_image = Image.open(BytesIO(response.body))
654        assert original_image.size > returned_image.size
655
656        # now, ask for a size that is bigger than the original image,
657        # image will be the same size, as we do not "grow" its size
658        request.GET['size'] = original_image.size[0] + 1000
659        response = user_views.profile_picture(user, request)
660        assert isinstance(response, Response)
661        assert response.status_int == 200
662        assert response.content_type == 'image'
663        # now the size of the original image is bigger
664        returned_image = Image.open(BytesIO(response.body))
665        assert original_image.size == returned_image.size
666
667    def test_edit_profile_get(self, dummy_request, john):
668        """
669        GET request to the edit profile page, returns the form ready to
670        be rendered
671        """
672        request = dummy_request
673        user = john
674        response = user_views.edit_profile(user, request)
675        assert isinstance(response['form'], OWFormRenderer)
676        # no errors in the form (first load)
677        assert response['form'].errorlist() == ''
678        # the form carries along the proper data keys, taken from the
679        # loaded user profile
680        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
681                'birth_date', 'height', 'weight', 'gender', 'timezone',
682                'locale']
683        assert list(response['form'].data.keys()) == data
684        # and check the email to see data is properly loaded
685        assert response['form'].data['email'] == 'john.doe@example.net'
686        assert response['timezones'] == common_timezones
687        assert response[
688            'available_locale_names'] == get_available_locale_names()
689        assert response['current_locale'] == request.registry.settings[
690            'pyramid.default_locale_name']
691
692    def test_edit_profile_post_ok(self, profile_post_request, john):
693        request = profile_post_request
694        user = john
695        # Update the bio field
696        bio = 'Some text about this user'
697        request.POST['bio'] = bio
698        response = user_views.edit_profile(user, request)
699        assert isinstance(response, HTTPFound)
700        assert response.location == request.resource_url(user, 'profile')
701        assert user.bio == bio
702
703    def test_edit_profile_post_ok_change_locale(
704            self, profile_post_request, john):
705        request = profile_post_request
706        user = john
707        # Update the locale
708        request.POST['locale'] = 'es'
709        response = user_views.edit_profile(user, request)
710        assert isinstance(response, HTTPFound)
711        assert response.location == request.resource_url(user, 'profile')
712        assert user.locale == 'es'
713
714    def test_edit_profile_post_ok_invalid_locale(
715            self, profile_post_request, john):
716        request = profile_post_request
717        user = john
718        # Update the locale with an invalid option
719        request.POST['locale'] = 'XX'
720        response = user_views.edit_profile(user, request)
721        assert isinstance(response['form'], OWFormRenderer)
722        # as an error happened, the current_locale had not changed
723        assert response['form'].errors == {
724            'locale': "Value must be one of: en; es (not 'XX')"}
725        assert user.locale == 'en'
726
727    def test_edit_profile_post_missing_required(
728            self, profile_post_request, john):
729        request = profile_post_request
730        request.POST['email'] = ''
731        user = john
732        response = user_views.edit_profile(user, request)
733        assert isinstance(response['form'], OWFormRenderer)
734        # error on the missing email field
735        error = u'Please enter an email address'
736        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
737        assert response['form'].errorlist() == html_error
738        assert response['form'].errors_for('email') == [error]
739
740    def test_edit_profile_post_ok_picture_empty_bytes(
741            self, profile_post_request, john):
742        """
743        POST request with an empty picture, the content of
744        request['POST'].picture is a empty bytes string (b'') which triggers
745        a bug in formencode, we put a fix in place, test that
746        (more in ow.user.views.edit_profile)
747        """
748        # for the purposes of this test, we can mock the picture
749        picture = Mock()
750        john.picture = picture
751        request = profile_post_request
752        user = john
753        # Mimic what happens when a picture is not provided by the user
754        request.POST['picture'] = b''
755        response = user_views.edit_profile(user, request)
756        assert isinstance(response, HTTPFound)
757        assert response.location == request.resource_url(user, 'profile')
758        assert user.picture == picture
759
760    def test_edit_profile_post_ok_missing_picture(
761            self, profile_post_request, john):
762        """
763        POST request without picture
764        """
765        # for the purposes of this test, we can mock the picture
766        picture = Mock()
767        john.picture = picture
768        request = profile_post_request
769        user = john
770        # No pic is provided in the request POST values
771        del request.POST['picture']
772        response = user_views.edit_profile(user, request)
773        assert isinstance(response, HTTPFound)
774        assert response.location == request.resource_url(user, 'profile')
775        assert user.picture == picture
776
777    def test_edit_profile_post_ok_nickname(self, profile_post_request, john):
778        """
779        User with a nickname set saves profile without changing the profile,
780        we have to be sure there are no "nickname already in use" errors
781        """
782        request = profile_post_request
783        user = john
784        user.nickname = 'mr_jones'
785        # add the nickname, the default post request has not a nickname set
786        request.POST['nickname'] = 'mr_jones'
787        response = user_views.edit_profile(user, request)
788        assert isinstance(response, HTTPFound)
789        assert response.location == request.resource_url(user, 'profile')
790
791    def test_change_password_get(self, dummy_request, john):
792        request = dummy_request
793        user = john
794        response = user_views.change_password(user, request)
795        assert isinstance(response['form'], OWFormRenderer)
796        # no errors in the form (first load)
797        assert response['form'].errorlist() == ''
798
799    def test_change_password_post_ok(self, passwd_post_request, john):
800        request = passwd_post_request
801        user = john
802        request.POST['old_password'] = 's3cr3t'
803        request.POST['password'] = 'h1dd3n s3cr3t'
804        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
805        response = user_views.change_password(user, request)
806        assert isinstance(response, HTTPFound)
807        assert response.location == request.resource_url(user, 'profile')
808        # password was changed
809        assert not user.check_password('s3cr3t')
810        assert user.check_password('h1dd3n s3cr3t')
811
812    def test_change_password_post_no_values(self, passwd_post_request, john):
813        request = passwd_post_request
814        user = john
815        response = user_views.change_password(user, request)
816        assert isinstance(response['form'], OWFormRenderer)
817        error = u'Please enter a value'
818        html_error = u'<ul class="error">'
819        html_error += ('<li>' + error + '</li>') * 3  # 3 fields
820        html_error += '</ul>'
821        errorlist = response['form'].errorlist().replace('\n', '')
822        assert errorlist == html_error
823        assert response['form'].errors_for('old_password') == [error]
824        assert response['form'].errors_for('password') == [error]
825        assert response['form'].errors_for('password_confirm') == [error]
826        # password was not changed
827        assert user.check_password('s3cr3t')
828
829    def test_change_password_post_bad_old_password(
830            self, passwd_post_request, john):
831        request = passwd_post_request
832        user = john
833        request.POST['old_password'] = 'FAIL PASSWORD'
834        request.POST['password'] = 'h1dd3n s3cr3t'
835        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
836        response = user_views.change_password(user, request)
837        assert isinstance(response['form'], OWFormRenderer)
838        error = u'The given password does not match the existing one '
839        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
840        assert response['form'].errorlist() == html_error
841        assert response['form'].errors_for('old_password') == [error]
842        # password was not changed
843        assert user.check_password('s3cr3t')
844        assert not user.check_password('h1dd3n s3cr3t')
845
846    def test_change_password_post_password_mismatch(
847            self, passwd_post_request, john):
848        request = passwd_post_request
849        user = john
850        request.POST['old_password'] = 's3cr3t'
851        request.POST['password'] = 'h1dd3n s3cr3ts'
852        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
853        response = user_views.change_password(user, request)
854        assert isinstance(response['form'], OWFormRenderer)
855        error = u'Fields do not match'
856        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
857        assert response['form'].errorlist() == html_error
858        assert response['form'].errors_for('password_confirm') == [error]
859        # password was not changed
860        assert user.check_password('s3cr3t')
861        assert not user.check_password('h1dd3n s3cr3t')
862
863    def test_signup_get(self, dummy_request):
864        request = dummy_request
865        response = user_views.signup(request.root, request)
866        assert isinstance(response['form'], OWFormRenderer)
867        # no errors in the form (first load)
868        assert response['form'].errorlist() == ''
869
870    @patch('ow.views.user.send_verification_email')
871    def test_signup_post_ok(self, sve, signup_post_request):
872        request = signup_post_request
873        assert 'jack.black@example.net' not in request.root.emails
874        assert 'JackBlack' not in request.root.all_nicknames
875        response = user_views.signup(request.root, request)
876        assert isinstance(response, HTTPFound)
877        assert response.location == request.resource_url(request.root)
878        assert 'jack.black@example.net' in request.root.emails
879        assert 'JackBlack' in request.root.all_nicknames
880        # user is in "unverified" state
881        user = request.root.get_user_by_email('jack.black@example.net')
882        assert not user.verified
883        assert isinstance(user.verification_token, UUID)
884        # also, we sent an email to that user
885        sve.assert_called_once_with(request, user)
886
887    def test_signup_missing_required(self, signup_post_request):
888        request = signup_post_request
889        request.POST['email'] = ''
890        assert 'jack.black@example.net' not in request.root.emails
891        assert 'JackBlack' not in request.root.all_nicknames
892        response = user_views.signup(request.root, request)
893        assert isinstance(response['form'], OWFormRenderer)
894        error = u'Please enter an email address'
895        html_error = '<ul class="error">'
896        html_error += '<li>' + error + '</li>'
897        html_error += '</ul>'
898        errorlist = response['form'].errorlist().replace('\n', '')
899        assert errorlist == html_error
900        assert response['form'].errors_for('email') == [error]
901        assert 'jack.black@example.net' not in request.root.emails
902        assert 'JackBlack' not in request.root.all_nicknames
903
904    def test_signup_existing_nickname(self, signup_post_request, john):
905        request = signup_post_request
906        # assign john a nickname first
907        john.nickname = 'john'
908        # now set it for the POST request
909        request.POST['nickname'] = 'john'
910        # check jack is not there yet
911        assert 'jack.black@example.net' not in request.root.emails
912        assert 'JackBlack' not in request.root.all_nicknames
913        # now signup as jack, but trying to set the nickname 'john'
914        response = user_views.signup(request.root, request)
915        assert isinstance(response['form'], OWFormRenderer)
916        error = u'Another user is already using the nickname john'
917        html_error = '<ul class="error">'
918        html_error += '<li>' + error + '</li>'
919        html_error += '</ul>'
920        errorlist = response['form'].errorlist().replace('\n', '')
921        assert errorlist == html_error
922        assert response['form'].errors_for('nickname') == [error]
923        # all the errors, and jack is not there
924        assert 'jack.black@example.net' not in request.root.emails
925        assert 'JackBlack' not in request.root.all_nicknames
926
927    def test_signup_existing_email(self, signup_post_request):
928        request = signup_post_request
929        request.POST['email'] = 'john.doe@example.net'
930        assert 'jack.black@example.net' not in request.root.emails
931        assert 'JackBlack' not in request.root.all_nicknames
932        response = user_views.signup(request.root, request)
933        assert isinstance(response['form'], OWFormRenderer)
934        error = u'Another user is already registered with the email '
935        error += u'john.doe@example.net'
936        html_error = '<ul class="error">'
937        html_error += '<li>' + error + '</li>'
938        html_error += '</ul>'
939        errorlist = response['form'].errorlist().replace('\n', '')
940        assert errorlist == html_error
941        assert response['form'].errors_for('email') == [error]
942        assert 'jack.black@example.net' not in request.root.emails
943        assert 'JackBlack' not in request.root.all_nicknames
944
945    def test_week_stats_no_stats(self, dummy_request, john):
946        response = user_views.week_stats(john, dummy_request)
947        assert isinstance(response, Response)
948        assert response.content_type == 'application/json'
949        # the body is a valid json-encoded stream
950        obj = json.loads(response.body)
951        assert obj == [
952            {'distance': 0, 'elevation': 0, 'name': 'Mon',
953             'time': '00', 'workouts': 0},
954            {'distance': 0, 'elevation': 0, 'name': 'Tue',
955             'time': '00', 'workouts': 0},
956            {'distance': 0, 'elevation': 0, 'name': 'Wed',
957             'time': '00', 'workouts': 0},
958            {'distance': 0, 'elevation': 0, 'name': 'Thu',
959             'time': '00', 'workouts': 0},
960            {'distance': 0, 'elevation': 0, 'name': 'Fri',
961             'time': '00', 'workouts': 0},
962            {'distance': 0, 'elevation': 0, 'name': 'Sat',
963             'time': '00', 'workouts': 0},
964            {'distance': 0, 'elevation': 0, 'name': 'Sun',
965             'time': '00', 'workouts': 0}
966        ]
967
968    def test_week_stats(self, dummy_request, john):
969        workout = Workout(
970            start=datetime.now(timezone.utc),
971            duration=timedelta(minutes=60),
972            distance=30,
973            elevation=540
974        )
975        john.add_workout(workout)
976        response = user_views.week_stats(john, dummy_request)
977        assert isinstance(response, Response)
978        assert response.content_type == 'application/json'
979        # the body is a valid json-encoded stream
980        obj = json.loads(response.body)
981        assert len(obj) == 7
982        for day in obj:
983            if datetime.now(timezone.utc).strftime('%a') == day['name']:
984                day['distance'] == 30
985                day['elevation'] == 540
986                day['time'] == '01'
987                day['workouts'] == 1
988            else:
989                day['distance'] == 0
990                day['elevation'] == 0
991                day['time'] == '00'
992                day['workouts'] == 0
Note: See TracBrowser for help on using the repository browser.