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

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

(#69) Added translations for User gender.
(+ added a third gender option, "robot" ;-D)

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