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

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

Fixed time-based bug on the dashboard view tests

  • Property mode set to 100644
File size: 41.7 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        now = datetime.now(timezone.utc)
294        request = dummy_request
295        response = user_views.dashboard(john, request)
296        assert len(response) == 6
297        assert 'month_name' in response.keys()
298        assert response['current_year'] == now.year
299        assert response['current_day_name'] == now.strftime('%a')
300        # this user has a single workout, in 2015
301        assert response['viewing_year'] == 2015
302        assert response['viewing_month'] == 6
303        assert response['workouts'] == [w for w in john.workouts()]
304
305    def test_dashboard_year(self, dummy_request, john):
306        """
307        Renders the user dashboard for a chosen year.
308        """
309        now = datetime.now(timezone.utc)
310        request = dummy_request
311        # first test the year for which we know there is a workout
312        request.GET['year'] = 2015
313        response = user_views.dashboard(john, request)
314        assert len(response) == 6
315        assert 'month_name' in response.keys()
316        assert response['current_year'] == now.year
317        assert response['current_day_name'] == now.strftime('%a')
318        # this user has a single workout, in 2015
319        assert response['viewing_year'] == 2015
320        assert response['viewing_month'] == 6
321        assert response['workouts'] == [w for w in john.workouts()]
322        # now, a year we know there is no workout info
323        request.GET['year'] = 2000
324        response = user_views.dashboard(john, request)
325        assert len(response) == 6
326        assert 'month_name' in response.keys()
327        assert response['current_year'] == now.year
328        assert response['current_day_name'] == now.strftime('%a')
329        # this user has a single workout, in 2015
330        assert response['viewing_year'] == 2000
331        # we have no data for that year and we didn't ask for a certain month,
332        # so the passing value for that is None
333        assert response['viewing_month'] is None
334        assert response['workouts'] == []
335
336    def test_dashboard_year_month(self, dummy_request, john):
337        """
338        Renders the user dashboard for a chosen year and month.
339        """
340        now = datetime.now(timezone.utc)
341        request = dummy_request
342        # first test the year/month for which we know there is a workout
343        request.GET['year'] = 2015
344        request.GET['month'] = 6
345        response = user_views.dashboard(john, request)
346        assert len(response) == 6
347        assert 'month_name' in response.keys()
348        assert response['current_year'] == now.year
349        assert response['current_day_name'] == now.strftime('%a')
350        # this user has a single workout, in 2015
351        assert response['viewing_year'] == 2015
352        assert response['viewing_month'] == 6
353        assert response['workouts'] == [w for w in john.workouts()]
354        # now, change month to one without values
355        request.GET['month'] = 2
356        response = user_views.dashboard(john, request)
357        assert len(response) == 6
358        assert 'month_name' in response.keys()
359        assert response['current_year'] == now.year
360        assert response['current_day_name'] == now.strftime('%a')
361        # this user has a single workout, in 2015
362        assert response['viewing_year'] == 2015
363        assert response['viewing_month'] == 2
364        assert response['workouts'] == []
365        # now the month with data, but in a different year
366        request.GET['year'] = 2010
367        request.GET['month'] = 6
368        response = user_views.dashboard(john, request)
369        assert len(response) == 6
370        assert 'month_name' in response.keys()
371        assert response['current_year'] == now.year
372        assert response['current_day_name'] == now.strftime('%a')
373        # this user has a single workout, in 2015
374        assert response['viewing_year'] == 2010
375        assert response['viewing_month'] == 6
376        assert response['workouts'] == []
377
378    def test_dashboard_month(self, dummy_request, john):
379        """
380        Passing a month without a year when rendering the dashboard. The last
381        year for which workout data is available is assumed
382        """
383        now = datetime.now(timezone.utc)
384        request = dummy_request
385        # Set a month without workout data
386        request.GET['month'] = 5
387        response = user_views.dashboard(john, request)
388        assert len(response) == 6
389        assert 'month_name' in response.keys()
390        assert response['current_year'] == now.year
391        assert response['current_day_name'] == now.strftime('%a')
392        # this user has a single workout, in 2015
393        assert response['viewing_year'] == 2015
394        assert response['viewing_month'] == 5
395        assert response['workouts'] == []
396        # now a month with data
397        request.GET['month'] = 6
398        response = user_views.dashboard(john, request)
399        assert len(response) == 6
400        assert 'month_name' in response.keys()
401        assert response['current_year'] == now.year
402        assert response['current_day_name'] == now.strftime('%a')
403        # this user has a single workout, in 2015
404        assert response['viewing_year'] == 2015
405        assert response['viewing_month'] == 6
406        assert response['workouts'] == [w for w in john.workouts()]
407
408    def test_profile(self, dummy_request, john):
409        """
410        Renders the user profile page
411        """
412        request = dummy_request
413        # profile page for the current day (no workouts avalable)
414        response = user_views.profile(john, request)
415        assert len(response.keys()) == 6
416        current_month = datetime.now(timezone.utc).strftime('%Y-%m')
417        assert response['user'] == john
418        assert response['user_gender'] == 'Robot'
419        assert response['current_month'] == current_month
420        assert response['current_week'] is None
421        assert response['workouts'] == []
422        assert response['totals'] == {
423            'distance': Decimal(0),
424            'time': timedelta(0),
425            'elevation': Decimal(0)
426        }
427        # profile page for a previous date, that has workouts
428        request.GET['year'] = 2015
429        request.GET['month'] = 6
430        response = user_views.profile(john, request)
431        assert len(response.keys()) == 6
432        assert response['user'] == john
433        assert response['user_gender'] == 'Robot'
434        assert response['current_month'] == '2015-06'
435        assert response['current_week'] is None
436        workouts = john.workouts(2015, 6)
437        assert response['workouts'] == workouts
438        assert response['totals'] == {
439            'distance': workouts[0].distance,
440            'time': workouts[0].duration,
441            'elevation': Decimal(0)
442        }
443        # same, passing a week, first on a week without workouts
444        request.GET['year'] = 2015
445        request.GET['month'] = 6
446        request.GET['week'] = 25
447        response = user_views.profile(john, request)
448        assert len(response.keys()) == 6
449        assert response['user'] == john
450        assert response['user_gender'] == 'Robot'
451        assert response['current_month'] == '2015-06'
452        assert response['current_week'] == 25
453        assert response['workouts'] == []
454        assert response['totals'] == {
455            'distance': Decimal(0),
456            'time': timedelta(0),
457            'elevation': Decimal(0)
458        }
459        # now in a week with workouts
460        request.GET['year'] = 2015
461        request.GET['month'] = 6
462        request.GET['week'] = 26
463        response = user_views.profile(john, request)
464        assert len(response.keys()) == 6
465        assert response['user'] == john
466        assert response['user_gender'] == 'Robot'
467        assert response['current_month'] == '2015-06'
468        assert response['current_week'] == 26
469        workouts = john.workouts(2015, 6)
470        assert response['workouts'] == workouts
471        assert response['totals'] == {
472            'distance': workouts[0].distance,
473            'time': workouts[0].duration,
474            'elevation': Decimal(0)
475        }
476
477    def test_login_get(self, dummy_request):
478        """
479        GET request to access the login page
480        """
481        request = dummy_request
482        response = user_views.login(request.root, request)
483        assert response['message'] == ''
484        assert response['email'] == ''
485        assert response['password'] == ''
486        assert response['redirect_url'] == request.resource_url(request.root)
487        assert response['resend_verify_link'] is None
488
489    def test_login_get_return_to(self, dummy_request, john):
490        """
491        GET request to access the login page, if there is a page set to where
492        the user should be sent to, the response "redirect_url" key will have
493        such url
494        """
495        request = dummy_request
496        workout = john.workouts()[0]
497        workout_url = request.resource_url(workout)
498        request.params['return_to'] = workout_url
499        response = user_views.login(request.root, request)
500        assert response['redirect_url'] == workout_url
501
502    def test_login_get_GET_messages(self, dummy_request):
503        """
504        GET request to access the login page, passing as a GET parameter
505        a "message key" so a given message is shown to the user
506        """
507        request = dummy_request
508        # first try with the keys we know there are messages associated to
509        request.GET['message'] = 'already-verified'
510        response = user_views.login(request.root, request)
511        assert response['message'] == 'User has been verified already'
512        request.GET['message'] = 'link-sent'
513        response = user_views.login(request.root, request)
514        assert response['message'] == (
515            'Verification link sent, please check your inbox')
516        request.GET['message'] = 'max-tokens-sent'
517        response = user_views.login(request.root, request)
518        assert response['message'] == (
519            'We already sent you the verification link more than three times')
520
521        # now try with a key that has no message assigned
522        request.GET['message'] = 'invalid-message-key'
523        response = user_views.login(request.root, request)
524        assert response['message'] == ''
525
526    def test_login_get_GET_email(self, dummy_request):
527        """
528        GET request to access the login page, passing as a GET parameter
529        an email address. That email is used to automatically fill in the
530        email input in the login form.
531        """
532        request = dummy_request
533        # first try with the keys we know there are messages associated to
534        request.GET['email'] = 'user@example.net'
535        response = user_views.login(request.root, request)
536        assert response['email'] == 'user@example.net'
537
538    def test_login_post_wrong_email(self, dummy_request):
539        request = dummy_request
540        request.method = 'POST'
541        request.POST['submit'] = True
542        request.POST['email'] = 'jack@example.net'
543        response = user_views.login(request.root, request)
544        assert response['message'] == u'Wrong email address'
545
546    def test_login_post_wrong_password(self, dummy_request):
547        request = dummy_request
548        request.method = 'POST'
549        request.POST['submit'] = True
550        request.POST['email'] = 'john.doe@example.net'
551        request.POST['password'] = 'badpassword'
552        # verify the user first
553        request.root.users[0].verified = True
554        response = user_views.login(request.root, request)
555        assert response['message'] == u'Wrong password'
556
557    @patch('ow.views.user.remember')
558    def test_login_post_unverified(self, rem, dummy_request, john):
559        request = dummy_request
560        request.method = 'POST'
561        request.POST['submit'] = True
562        request.POST['email'] = 'john.doe@example.net'
563        request.POST['password'] = 's3cr3t'
564        response = user_views.login(request.root, request)
565        assert response['message'] == u'You have to verify your account first'
566
567    @patch('ow.views.user.remember')
568    def test_login_post_ok(self, rem, dummy_request, john):
569        request = dummy_request
570        request.method = 'POST'
571        request.POST['submit'] = True
572        request.POST['email'] = 'john.doe@example.net'
573        request.POST['password'] = 's3cr3t'
574        # verify the user first
575        john.verified = True
576        response = user_views.login(request.root, request)
577        assert isinstance(response, HTTPFound)
578        assert rem.called
579        assert response.location == request.resource_url(john)
580        # the response headers contain the proper set_cookie for the default
581        # locale
582        default_locale_name = request.registry.settings[
583            'pyramid.default_locale_name']
584        expected_locale_header = '_LOCALE_=' + default_locale_name + '; Path=/'
585        assert response.headers['Set-Cookie'] == expected_locale_header
586
587    @patch('ow.views.user.remember')
588    def test_login_post_ok_set_locale(self, rem, dummy_request, john):
589        # same as the previous test, but this time the user has set a
590        # locale different than the default one
591        request = dummy_request
592        request.method = 'POST'
593        request.POST['submit'] = True
594        request.POST['email'] = 'john.doe@example.net'
595        request.POST['password'] = 's3cr3t'
596        # verify the user first
597        john.verified = True
598        # set the locale
599        john.locale = 'es'
600        response = user_views.login(request.root, request)
601        assert isinstance(response, HTTPFound)
602        assert rem.called
603        assert response.location == request.resource_url(john)
604        # the response headers contain the proper set_cookie for the user
605        # locale setting
606        expected_locale_header = '_LOCALE_=es; Path=/'
607        assert response.headers['Set-Cookie'] == expected_locale_header
608
609    @patch('ow.views.user.forget')
610    def test_logout(self, forg, dummy_request):
611        request = dummy_request
612        response = user_views.logout(request.root, request)
613        assert isinstance(response, HTTPFound)
614        assert forg.called
615        assert response.location == request.resource_url(request.root)
616        # the response headers contain the needed Set-Cookie header that
617        # invalidates the _LOCALE_ cookie, preventing problems with users
618        # sharing the same web browser (one locale setting being set for
619        # another user)
620        expected_locale_header = '_LOCALE_=; Max-Age=0; Path=/; expires='
621        assert expected_locale_header in response.headers['Set-Cookie']
622
623    extensions = ('png', 'jpg', 'jpeg', 'gif')
624
625    @pytest.mark.parametrize('extension', extensions)
626    def test_profile_picture(self, extension, dummy_request, john):
627        """
628        GET request to get the profile picture of an user.
629        """
630        request = dummy_request
631        # Get the user
632        user = john
633        # Get the path to the image, then open it and copy it to a new Blob
634        # object
635        path = 'fixtures/image.' + extension
636        image_path = os.path.join(
637            os.path.dirname(os.path.dirname(__file__)), path)
638        blob = Blob()
639        with open(image_path, 'rb') as infile, blob.open('w') as out:
640            infile.seek(0)
641            copyfileobj(infile, out)
642
643        # Set the blob with the picture
644        user.picture = blob
645
646        # Call the profile_picture view
647        response = user_views.profile_picture(user, request)
648        assert isinstance(response, Response)
649        assert response.status_int == 200
650        assert response.content_type == 'image'
651        # as we did not pass a specific size as a get parameter, the size is
652        # the same as the original image
653        original_image = Image.open(image_path)
654        returned_image = Image.open(BytesIO(response.body))
655        assert original_image.size == returned_image.size
656
657        # now, ask for a smaller image
658        request.GET['size'] = original_image.size[0] - 20
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        # now, ask for a size that is bigger than the original image,
668        # image will be the same size, as we do not "grow" its size
669        request.GET['size'] = original_image.size[0] + 1000
670        response = user_views.profile_picture(user, request)
671        assert isinstance(response, Response)
672        assert response.status_int == 200
673        assert response.content_type == 'image'
674        # now the size of the original image is bigger
675        returned_image = Image.open(BytesIO(response.body))
676        assert original_image.size == returned_image.size
677
678    def test_edit_profile_get(self, dummy_request, john):
679        """
680        GET request to the edit profile page, returns the form ready to
681        be rendered
682        """
683        request = dummy_request
684        user = john
685        response = user_views.edit_profile(user, request)
686        assert isinstance(response['form'], OWFormRenderer)
687        # no errors in the form (first load)
688        assert response['form'].errorlist() == ''
689        # the form carries along the proper data keys, taken from the
690        # loaded user profile
691        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
692                'birth_date', 'height', 'weight', 'gender', 'timezone',
693                'locale']
694        assert list(response['form'].data.keys()) == data
695        # and check the email to see data is properly loaded
696        assert response['form'].data['email'] == 'john.doe@example.net'
697        assert response['timezones'] == common_timezones
698        assert response[
699            'available_locale_names'] == get_available_locale_names()
700        assert response['current_locale'] == request.registry.settings[
701            'pyramid.default_locale_name']
702
703    def test_edit_profile_post_ok(self, profile_post_request, john):
704        request = profile_post_request
705        user = john
706        # Update the bio field
707        bio = 'Some text about this user'
708        request.POST['bio'] = bio
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.bio == bio
713
714    def test_edit_profile_post_ok_change_locale(
715            self, profile_post_request, john):
716        request = profile_post_request
717        user = john
718        # Update the locale
719        request.POST['locale'] = 'es'
720        response = user_views.edit_profile(user, request)
721        assert isinstance(response, HTTPFound)
722        assert response.location == request.resource_url(user, 'profile')
723        assert user.locale == 'es'
724
725    def test_edit_profile_post_ok_invalid_locale(
726            self, profile_post_request, john):
727        request = profile_post_request
728        user = john
729        # Update the locale with an invalid option
730        request.POST['locale'] = 'XX'
731        response = user_views.edit_profile(user, request)
732        assert isinstance(response['form'], OWFormRenderer)
733        # as an error happened, the current_locale had not changed
734        assert response['form'].errors == {
735            'locale': "Value must be one of: en; es (not 'XX')"}
736        assert user.locale == 'en'
737
738    def test_edit_profile_post_missing_required(
739            self, profile_post_request, john):
740        request = profile_post_request
741        request.POST['email'] = ''
742        user = john
743        response = user_views.edit_profile(user, request)
744        assert isinstance(response['form'], OWFormRenderer)
745        # error on the missing email field
746        error = u'Please enter an email address'
747        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
748        assert response['form'].errorlist() == html_error
749        assert response['form'].errors_for('email') == [error]
750
751    def test_edit_profile_post_ok_picture_empty_bytes(
752            self, profile_post_request, john):
753        """
754        POST request with an empty picture, the content of
755        request['POST'].picture is a empty bytes string (b'') which triggers
756        a bug in formencode, we put a fix in place, test that
757        (more in ow.user.views.edit_profile)
758        """
759        # for the purposes of this test, we can mock the picture
760        picture = Mock()
761        john.picture = picture
762        request = profile_post_request
763        user = john
764        # Mimic what happens when a picture is not provided by the user
765        request.POST['picture'] = b''
766        response = user_views.edit_profile(user, request)
767        assert isinstance(response, HTTPFound)
768        assert response.location == request.resource_url(user, 'profile')
769        assert user.picture == picture
770
771    def test_edit_profile_post_ok_missing_picture(
772            self, profile_post_request, john):
773        """
774        POST request without picture
775        """
776        # for the purposes of this test, we can mock the picture
777        picture = Mock()
778        john.picture = picture
779        request = profile_post_request
780        user = john
781        # No pic is provided in the request POST values
782        del request.POST['picture']
783        response = user_views.edit_profile(user, request)
784        assert isinstance(response, HTTPFound)
785        assert response.location == request.resource_url(user, 'profile')
786        assert user.picture == picture
787
788    def test_edit_profile_post_ok_nickname(self, profile_post_request, john):
789        """
790        User with a nickname set saves profile without changing the profile,
791        we have to be sure there are no "nickname already in use" errors
792        """
793        request = profile_post_request
794        user = john
795        user.nickname = 'mr_jones'
796        # add the nickname, the default post request has not a nickname set
797        request.POST['nickname'] = 'mr_jones'
798        response = user_views.edit_profile(user, request)
799        assert isinstance(response, HTTPFound)
800        assert response.location == request.resource_url(user, 'profile')
801
802    def test_change_password_get(self, dummy_request, john):
803        request = dummy_request
804        user = john
805        response = user_views.change_password(user, request)
806        assert isinstance(response['form'], OWFormRenderer)
807        # no errors in the form (first load)
808        assert response['form'].errorlist() == ''
809
810    def test_change_password_post_ok(self, passwd_post_request, john):
811        request = passwd_post_request
812        user = john
813        request.POST['old_password'] = 's3cr3t'
814        request.POST['password'] = 'h1dd3n s3cr3t'
815        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
816        response = user_views.change_password(user, request)
817        assert isinstance(response, HTTPFound)
818        assert response.location == request.resource_url(user, 'profile')
819        # password was changed
820        assert not user.check_password('s3cr3t')
821        assert user.check_password('h1dd3n s3cr3t')
822
823    def test_change_password_post_no_values(self, passwd_post_request, john):
824        request = passwd_post_request
825        user = john
826        response = user_views.change_password(user, request)
827        assert isinstance(response['form'], OWFormRenderer)
828        error = u'Please enter a value'
829        html_error = u'<ul class="error">'
830        html_error += ('<li>' + error + '</li>') * 3  # 3 fields
831        html_error += '</ul>'
832        errorlist = response['form'].errorlist().replace('\n', '')
833        assert errorlist == html_error
834        assert response['form'].errors_for('old_password') == [error]
835        assert response['form'].errors_for('password') == [error]
836        assert response['form'].errors_for('password_confirm') == [error]
837        # password was not changed
838        assert user.check_password('s3cr3t')
839
840    def test_change_password_post_bad_old_password(
841            self, passwd_post_request, john):
842        request = passwd_post_request
843        user = john
844        request.POST['old_password'] = 'FAIL PASSWORD'
845        request.POST['password'] = 'h1dd3n s3cr3t'
846        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
847        response = user_views.change_password(user, request)
848        assert isinstance(response['form'], OWFormRenderer)
849        error = u'The given password does not match the existing one '
850        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
851        assert response['form'].errorlist() == html_error
852        assert response['form'].errors_for('old_password') == [error]
853        # password was not changed
854        assert user.check_password('s3cr3t')
855        assert not user.check_password('h1dd3n s3cr3t')
856
857    def test_change_password_post_password_mismatch(
858            self, passwd_post_request, john):
859        request = passwd_post_request
860        user = john
861        request.POST['old_password'] = 's3cr3t'
862        request.POST['password'] = 'h1dd3n s3cr3ts'
863        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
864        response = user_views.change_password(user, request)
865        assert isinstance(response['form'], OWFormRenderer)
866        error = u'Fields do not match'
867        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
868        assert response['form'].errorlist() == html_error
869        assert response['form'].errors_for('password_confirm') == [error]
870        # password was not changed
871        assert user.check_password('s3cr3t')
872        assert not user.check_password('h1dd3n s3cr3t')
873
874    def test_signup_get(self, dummy_request):
875        request = dummy_request
876        response = user_views.signup(request.root, request)
877        assert isinstance(response['form'], OWFormRenderer)
878        # no errors in the form (first load)
879        assert response['form'].errorlist() == ''
880
881    @patch('ow.views.user.send_verification_email')
882    def test_signup_post_ok(self, sve, signup_post_request):
883        request = signup_post_request
884        assert 'jack.black@example.net' not in request.root.emails
885        assert 'JackBlack' not in request.root.all_nicknames
886        response = user_views.signup(request.root, request)
887        assert isinstance(response, HTTPFound)
888        assert response.location == request.resource_url(request.root)
889        assert 'jack.black@example.net' in request.root.emails
890        assert 'JackBlack' in request.root.all_nicknames
891        # user is in "unverified" state
892        user = request.root.get_user_by_email('jack.black@example.net')
893        assert not user.verified
894        assert isinstance(user.verification_token, UUID)
895        # also, we sent an email to that user
896        sve.assert_called_once_with(request, user)
897
898    def test_signup_missing_required(self, signup_post_request):
899        request = signup_post_request
900        request.POST['email'] = ''
901        assert 'jack.black@example.net' not in request.root.emails
902        assert 'JackBlack' not in request.root.all_nicknames
903        response = user_views.signup(request.root, request)
904        assert isinstance(response['form'], OWFormRenderer)
905        error = u'Please enter an email address'
906        html_error = '<ul class="error">'
907        html_error += '<li>' + error + '</li>'
908        html_error += '</ul>'
909        errorlist = response['form'].errorlist().replace('\n', '')
910        assert errorlist == html_error
911        assert response['form'].errors_for('email') == [error]
912        assert 'jack.black@example.net' not in request.root.emails
913        assert 'JackBlack' not in request.root.all_nicknames
914
915    def test_signup_existing_nickname(self, signup_post_request, john):
916        request = signup_post_request
917        # assign john a nickname first
918        john.nickname = 'john'
919        # now set it for the POST request
920        request.POST['nickname'] = 'john'
921        # check jack is not there yet
922        assert 'jack.black@example.net' not in request.root.emails
923        assert 'JackBlack' not in request.root.all_nicknames
924        # now signup as jack, but trying to set the nickname 'john'
925        response = user_views.signup(request.root, request)
926        assert isinstance(response['form'], OWFormRenderer)
927        error = u'Another user is already using the nickname john'
928        html_error = '<ul class="error">'
929        html_error += '<li>' + error + '</li>'
930        html_error += '</ul>'
931        errorlist = response['form'].errorlist().replace('\n', '')
932        assert errorlist == html_error
933        assert response['form'].errors_for('nickname') == [error]
934        # all the errors, and jack is not there
935        assert 'jack.black@example.net' not in request.root.emails
936        assert 'JackBlack' not in request.root.all_nicknames
937
938    def test_signup_existing_email(self, signup_post_request):
939        request = signup_post_request
940        request.POST['email'] = 'john.doe@example.net'
941        assert 'jack.black@example.net' not in request.root.emails
942        assert 'JackBlack' not in request.root.all_nicknames
943        response = user_views.signup(request.root, request)
944        assert isinstance(response['form'], OWFormRenderer)
945        error = u'Another user is already registered with the email '
946        error += u'john.doe@example.net'
947        html_error = '<ul class="error">'
948        html_error += '<li>' + error + '</li>'
949        html_error += '</ul>'
950        errorlist = response['form'].errorlist().replace('\n', '')
951        assert errorlist == html_error
952        assert response['form'].errors_for('email') == [error]
953        assert 'jack.black@example.net' not in request.root.emails
954        assert 'JackBlack' not in request.root.all_nicknames
955
956    def test_week_stats_no_stats(self, dummy_request, john):
957        response = user_views.week_stats(john, dummy_request)
958        assert isinstance(response, Response)
959        assert response.content_type == 'application/json'
960        # the body is a valid json-encoded stream
961        obj = json.loads(response.body)
962        assert obj == [
963            {'distance': 0, 'elevation': 0, 'name': 'Mon',
964             'time': '00', 'workouts': 0},
965            {'distance': 0, 'elevation': 0, 'name': 'Tue',
966             'time': '00', 'workouts': 0},
967            {'distance': 0, 'elevation': 0, 'name': 'Wed',
968             'time': '00', 'workouts': 0},
969            {'distance': 0, 'elevation': 0, 'name': 'Thu',
970             'time': '00', 'workouts': 0},
971            {'distance': 0, 'elevation': 0, 'name': 'Fri',
972             'time': '00', 'workouts': 0},
973            {'distance': 0, 'elevation': 0, 'name': 'Sat',
974             'time': '00', 'workouts': 0},
975            {'distance': 0, 'elevation': 0, 'name': 'Sun',
976             'time': '00', 'workouts': 0}
977        ]
978
979    def test_week_stats(self, dummy_request, john):
980        workout = Workout(
981            start=datetime.now(timezone.utc),
982            duration=timedelta(minutes=60),
983            distance=30,
984            elevation=540
985        )
986        john.add_workout(workout)
987        response = user_views.week_stats(john, dummy_request)
988        assert isinstance(response, Response)
989        assert response.content_type == 'application/json'
990        # the body is a valid json-encoded stream
991        obj = json.loads(response.body)
992        assert len(obj) == 7
993        for day in obj:
994            if datetime.now(timezone.utc).strftime('%a') == day['name']:
995                day['distance'] == 30
996                day['elevation'] == 540
997                day['time'] == '01'
998                day['workouts'] == 1
999            else:
1000                day['distance'] == 0
1001                day['elevation'] == 0
1002                day['time'] == '00'
1003                day['workouts'] == 0
Note: See TracBrowser for help on using the repository browser.