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

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

(#67) Allow users to send again the verification link (up to 3 times)
to the email address they provided when signing up.

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