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

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

Fixed missing translations for submit buttons all over the place.

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