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

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

(#29) Add user verification by email on signup.

From now on, when a new user signs up, we set the account into an "unverified"
state. In order to complete the signup procedure, the user has to click on a
link we send by email to the email address provided on signup.

IMPORTANT: A new dependency has been added, pyramid_mailer, so remember to
install it in any existing openworkouts environment (this is done automatically
if you use the ./bin/start script):

pip install pyramid_mailer

  • Property mode set to 100644
File size: 33.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 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    def test_dashboard_redirect_unauthenticated(self, root):
175        """
176        Anoymous access to the root object, send the user to the login page.
177
178        Instead of reusing the DummyRequest from the request fixture, we do
179        Mock fully the request here, because we need to use
180        authenticated_userid, which cannot be easily set in the DummyRequest
181        """
182        request = DummyRequest()
183        request.root = root
184        response = user_views.dashboard_redirect(root, request)
185        assert isinstance(response, HTTPFound)
186        assert response.location == request.resource_url(root, 'login')
187
188    def test_dashboard_redirect_authenticated(self, root, john):
189        """
190        Authenticated user accesing the root object, send the user to her
191        dashboard
192
193        Instead of reusing the DummyRequest from the request fixture, we do
194        Mock fully the request here, because we need to use
195        authenticated_userid, which cannot be easily set in the DummyRequest
196        """
197        alt_request = DummyRequest()
198        request = Mock()
199        request.root = root
200        request.authenticated_userid = str(john.uid)
201        request.resource_url = alt_request.resource_url
202        response = user_views.dashboard_redirect(root, request)
203        assert isinstance(response, HTTPFound)
204        assert response.location == request.resource_url(john)
205        # if authenticated_userid is the id of an user that does not exist
206        # anymore, we send the user to the logout page
207        request.authenticated_userid = 'faked-uid'
208        response = user_views.dashboard_redirect(root, request)
209        assert isinstance(response, HTTPFound)
210        assert response.location == request.resource_url(root, 'logout')
211
212    def test_dashboard(self, dummy_request, john):
213        """
214        Renders the user dashboard
215        """
216        request = dummy_request
217        response = user_views.dashboard(john, request)
218        assert len(response) == 6
219        assert 'month_name' in response.keys()
220        assert response['current_year'] == datetime.now().year
221        assert response['current_day_name'] == datetime.now().strftime('%a')
222        # this user has a single workout, in 2015
223        assert response['viewing_year'] == 2015
224        assert response['viewing_month'] == 6
225        assert response['workouts'] == [w for w in john.workouts()]
226
227    def test_dashboard_year(self, dummy_request, john):
228        """
229        Renders the user dashboard for a chosen year.
230        """
231        request = dummy_request
232        # first test the year for which we know there is a workout
233        request.GET['year'] = 2015
234        response = user_views.dashboard(john, request)
235        assert len(response) == 6
236        assert 'month_name' in response.keys()
237        assert response['current_year'] == datetime.now().year
238        assert response['current_day_name'] == datetime.now().strftime('%a')
239        # this user has a single workout, in 2015
240        assert response['viewing_year'] == 2015
241        assert response['viewing_month'] == 6
242        assert response['workouts'] == [w for w in john.workouts()]
243        # now, a year we know there is no workout info
244        request.GET['year'] = 2000
245        response = user_views.dashboard(john, request)
246        assert len(response) == 6
247        assert 'month_name' in response.keys()
248        assert response['current_year'] == datetime.now().year
249        assert response['current_day_name'] == datetime.now().strftime('%a')
250        # this user has a single workout, in 2015
251        assert response['viewing_year'] == 2000
252        # we have no data for that year and we didn't ask for a certain month,
253        # so the passing value for that is None
254        assert response['viewing_month'] is None
255        assert response['workouts'] == []
256
257    def test_dashboard_year_month(self, dummy_request, john):
258        """
259        Renders the user dashboard for a chosen year and month.
260        """
261        request = dummy_request
262        # first test the year/month for which we know there is a workout
263        request.GET['year'] = 2015
264        request.GET['month'] = 6
265        response = user_views.dashboard(john, request)
266        assert len(response) == 6
267        assert 'month_name' in response.keys()
268        assert response['current_year'] == datetime.now().year
269        assert response['current_day_name'] == datetime.now().strftime('%a')
270        # this user has a single workout, in 2015
271        assert response['viewing_year'] == 2015
272        assert response['viewing_month'] == 6
273        assert response['workouts'] == [w for w in john.workouts()]
274        # now, change month to one without values
275        request.GET['month'] = 2
276        response = user_views.dashboard(john, request)
277        assert len(response) == 6
278        assert 'month_name' in response.keys()
279        assert response['current_year'] == datetime.now().year
280        assert response['current_day_name'] == datetime.now().strftime('%a')
281        # this user has a single workout, in 2015
282        assert response['viewing_year'] == 2015
283        assert response['viewing_month'] == 2
284        assert response['workouts'] == []
285        # now the month with data, but in a different year
286        request.GET['year'] = 2010
287        request.GET['month'] = 6
288        response = user_views.dashboard(john, request)
289        assert len(response) == 6
290        assert 'month_name' in response.keys()
291        assert response['current_year'] == datetime.now().year
292        assert response['current_day_name'] == datetime.now().strftime('%a')
293        # this user has a single workout, in 2015
294        assert response['viewing_year'] == 2010
295        assert response['viewing_month'] == 6
296        assert response['workouts'] == []
297
298    def test_dashboard_month(self, dummy_request, john):
299        """
300        Passing a month without a year when rendering the dashboard. The last
301        year for which workout data is available is assumed
302        """
303        request = dummy_request
304        # Set a month without workout data
305        request.GET['month'] = 5
306        response = user_views.dashboard(john, request)
307        assert len(response) == 6
308        assert 'month_name' in response.keys()
309        assert response['current_year'] == datetime.now().year
310        assert response['current_day_name'] == datetime.now().strftime('%a')
311        # this user has a single workout, in 2015
312        assert response['viewing_year'] == 2015
313        assert response['viewing_month'] == 5
314        assert response['workouts'] == []
315        # now a month with data
316        request.GET['month'] = 6
317        response = user_views.dashboard(john, request)
318        assert len(response) == 6
319        assert 'month_name' in response.keys()
320        assert response['current_year'] == datetime.now().year
321        assert response['current_day_name'] == datetime.now().strftime('%a')
322        # this user has a single workout, in 2015
323        assert response['viewing_year'] == 2015
324        assert response['viewing_month'] == 6
325        assert response['workouts'] == [w for w in john.workouts()]
326
327    def test_profile(self, dummy_request, john):
328        """
329        Renders the user profile page
330        """
331        request = dummy_request
332        # profile page for the current day (no workouts avalable)
333        response = user_views.profile(john, request)
334        assert len(response.keys()) == 5
335        current_month = datetime.now(timezone.utc).strftime('%Y-%m')
336        assert response['user'] == john
337        assert response['current_month'] == current_month
338        assert response['current_week'] is None
339        assert response['workouts'] == []
340        assert response['totals'] == {
341            'distance': Decimal(0),
342            'time': timedelta(0),
343            'elevation': Decimal(0)
344        }
345        # profile page for a previous date, that has workouts
346        request.GET['year'] = 2015
347        request.GET['month'] = 6
348        response = user_views.profile(john, request)
349        assert len(response.keys()) == 5
350        assert response['user'] == john
351        assert response['current_month'] == '2015-06'
352        assert response['current_week'] is None
353        workouts = john.workouts(2015, 6)
354        assert response['workouts'] == workouts
355        assert response['totals'] == {
356            'distance': workouts[0].distance,
357            'time': workouts[0].duration,
358            'elevation': Decimal(0)
359        }
360        # same, passing a week, first on a week without workouts
361        request.GET['year'] = 2015
362        request.GET['month'] = 6
363        request.GET['week'] = 25
364        response = user_views.profile(john, request)
365        assert len(response.keys()) == 5
366        assert response['user'] == john
367        assert response['current_month'] == '2015-06'
368        assert response['current_week'] == 25
369        assert response['workouts'] == []
370        assert response['totals'] == {
371            'distance': Decimal(0),
372            'time': timedelta(0),
373            'elevation': Decimal(0)
374        }
375        # now in a week with workouts
376        request.GET['year'] = 2015
377        request.GET['month'] = 6
378        request.GET['week'] = 26
379        response = user_views.profile(john, request)
380        assert len(response.keys()) == 5
381        assert response['user'] == john
382        assert response['current_month'] == '2015-06'
383        assert response['current_week'] == 26
384        workouts = john.workouts(2015, 6)
385        assert response['workouts'] == workouts
386        assert response['totals'] == {
387            'distance': workouts[0].distance,
388            'time': workouts[0].duration,
389            'elevation': Decimal(0)
390        }
391
392    def test_login_get(self, dummy_request):
393        """
394        GET request to access the login page
395        """
396        request = dummy_request
397        response = user_views.login(request.root, request)
398        assert response['message'] == ''
399        assert response['email'] == ''
400        assert response['password'] == ''
401        assert response['redirect_url'] == request.resource_url(request.root)
402
403    def test_login_get_return_to(self, dummy_request, john):
404        """
405        GET request to access the login page, if there is a page set to where
406        the user should be sent to, the response "redirect_url" key will have
407        such url
408        """
409        request = dummy_request
410        workout = john.workouts()[0]
411        workout_url = request.resource_url(workout)
412        request.params['return_to'] = workout_url
413        response = user_views.login(request.root, request)
414        assert response['redirect_url'] == workout_url
415
416    def test_login_post_wrong_email(self, dummy_request):
417        request = dummy_request
418        request.method = 'POST'
419        request.POST['submit'] = True
420        request.POST['email'] = 'jack@example.net'
421        response = user_views.login(request.root, request)
422        assert response['message'] == u'Wrong email address'
423
424    def test_login_post_wrong_password(self, dummy_request):
425        request = dummy_request
426        request.method = 'POST'
427        request.POST['submit'] = True
428        request.POST['email'] = 'john.doe@example.net'
429        request.POST['password'] = 'badpassword'
430        # verify the user first
431        request.root.users[0].verified = True
432        response = user_views.login(request.root, request)
433        assert response['message'] == u'Wrong password'
434
435    @patch('ow.views.user.remember')
436    def test_login_post_unverified(self, rem, dummy_request, john):
437        request = dummy_request
438        request.method = 'POST'
439        request.POST['submit'] = True
440        request.POST['email'] = 'john.doe@example.net'
441        request.POST['password'] = 's3cr3t'
442        response = user_views.login(request.root, request)
443        assert response['message'] == u'You have to verify your account first'
444
445    @patch('ow.views.user.remember')
446    def test_login_post_ok(self, rem, dummy_request, john):
447        request = dummy_request
448        request.method = 'POST'
449        request.POST['submit'] = True
450        request.POST['email'] = 'john.doe@example.net'
451        request.POST['password'] = 's3cr3t'
452        # verify the user first
453        john.verified = True
454        response = user_views.login(request.root, request)
455        assert isinstance(response, HTTPFound)
456        assert rem.called
457        assert response.location == request.resource_url(john)
458
459    @patch('ow.views.user.forget')
460    def test_logout(self, forg, dummy_request):
461        request = dummy_request
462        response = user_views.logout(request.root, request)
463        assert isinstance(response, HTTPFound)
464        assert forg.called
465        assert response.location == request.resource_url(request.root)
466
467    extensions = ('png', 'jpg', 'jpeg', 'gif')
468
469    @pytest.mark.parametrize('extension', extensions)
470    def test_profile_picture(self, extension, dummy_request, john):
471        """
472        GET request to get the profile picture of an user.
473        """
474        request = dummy_request
475        # Get the user
476        user = john
477        # Get the path to the image, then open it and copy it to a new Blob
478        # object
479        path = 'fixtures/image.' + extension
480        image_path = os.path.join(
481            os.path.dirname(os.path.dirname(__file__)), path)
482        blob = Blob()
483        with open(image_path, 'rb') as infile, blob.open('w') as out:
484            infile.seek(0)
485            copyfileobj(infile, out)
486
487        # Set the blob with the picture
488        user.picture = blob
489
490        # Call the profile_picture view
491        response = user_views.profile_picture(user, request)
492        assert isinstance(response, Response)
493        assert response.status_int == 200
494        assert response.content_type == 'image'
495        # as we did not pass a specific size as a get parameter, the size is
496        # the same as the original image
497        original_image = Image.open(image_path)
498        returned_image = Image.open(BytesIO(response.body))
499        assert original_image.size == returned_image.size
500
501        # now, ask for a smaller image
502        request.GET['size'] = original_image.size[0] - 20
503        response = user_views.profile_picture(user, request)
504        assert isinstance(response, Response)
505        assert response.status_int == 200
506        assert response.content_type == 'image'
507        # now the size of the original image is bigger
508        returned_image = Image.open(BytesIO(response.body))
509        assert original_image.size > returned_image.size
510
511        # now, ask for a size that is bigger than the original image,
512        # image will be the same size, as we do not "grow" its size
513        request.GET['size'] = original_image.size[0] + 1000
514        response = user_views.profile_picture(user, request)
515        assert isinstance(response, Response)
516        assert response.status_int == 200
517        assert response.content_type == 'image'
518        # now the size of the original image is bigger
519        returned_image = Image.open(BytesIO(response.body))
520        assert original_image.size == returned_image.size
521
522    def test_edit_profile_get(self, dummy_request, john):
523        """
524        GET request to the edit profile page, returns the form ready to
525        be rendered
526        """
527        request = dummy_request
528        user = john
529        response = user_views.edit_profile(user, request)
530        assert isinstance(response['form'], OWFormRenderer)
531        # no errors in the form (first load)
532        assert response['form'].errorlist() == ''
533        # the form carries along the proper data keys, taken from the
534        # loaded user profile
535        data = ['firstname', 'lastname', 'email', 'nickname', 'bio',
536                'birth_date', 'height', 'weight', 'gender', 'timezone']
537        assert list(response['form'].data.keys()) == data
538        # and check the email to see data is properly loaded
539        assert response['form'].data['email'] == 'john.doe@example.net'
540
541    def test_edit_profile_post_ok(self, profile_post_request, john):
542        request = profile_post_request
543        user = john
544        # Update the bio field
545        bio = 'Some text about this user'
546        request.POST['bio'] = bio
547        response = user_views.edit_profile(user, request)
548        assert isinstance(response, HTTPFound)
549        assert response.location == request.resource_url(user, 'profile')
550        assert user.bio == bio
551
552    def test_edit_profile_post_missing_required(
553            self, profile_post_request, john):
554        request = profile_post_request
555        request.POST['email'] = ''
556        user = john
557        response = user_views.edit_profile(user, request)
558        assert isinstance(response['form'], OWFormRenderer)
559        # error on the missing email field
560        error = u'Please enter an email address'
561        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
562        assert response['form'].errorlist() == html_error
563        assert response['form'].errors_for('email') == [error]
564
565    def test_edit_profile_post_ok_picture_empty_bytes(
566            self, profile_post_request, john):
567        """
568        POST request with an empty picture, the content of
569        request['POST'].picture is a empty bytes string (b'') which triggers
570        a bug in formencode, we put a fix in place, test that
571        (more in ow.user.views.edit_profile)
572        """
573        # for the purposes of this test, we can mock the picture
574        picture = Mock()
575        john.picture = picture
576        request = profile_post_request
577        user = john
578        # Mimic what happens when a picture is not provided by the user
579        request.POST['picture'] = b''
580        response = user_views.edit_profile(user, request)
581        assert isinstance(response, HTTPFound)
582        assert response.location == request.resource_url(user, 'profile')
583        assert user.picture == picture
584
585    def test_edit_profile_post_ok_missing_picture(
586            self, profile_post_request, john):
587        """
588        POST request without picture
589        """
590        # for the purposes of this test, we can mock the picture
591        picture = Mock()
592        john.picture = picture
593        request = profile_post_request
594        user = john
595        # No pic is provided in the request POST values
596        del request.POST['picture']
597        response = user_views.edit_profile(user, request)
598        assert isinstance(response, HTTPFound)
599        assert response.location == request.resource_url(user, 'profile')
600        assert user.picture == picture
601
602    def test_edit_profile_post_ok_nickname(self, profile_post_request, john):
603        """
604        User with a nickname set saves profile without changing the profile,
605        we have to be sure there are no "nickname already in use" errors
606        """
607        request = profile_post_request
608        user = john
609        user.nickname = 'mr_jones'
610        # add the nickname, the default post request has not a nickname set
611        request.POST['nickname'] = 'mr_jones'
612        response = user_views.edit_profile(user, request)
613        assert isinstance(response, HTTPFound)
614        assert response.location == request.resource_url(user, 'profile')
615
616    def test_change_password_get(self, dummy_request, john):
617        request = dummy_request
618        user = john
619        response = user_views.change_password(user, request)
620        assert isinstance(response['form'], OWFormRenderer)
621        # no errors in the form (first load)
622        assert response['form'].errorlist() == ''
623
624    def test_change_password_post_ok(self, passwd_post_request, john):
625        request = passwd_post_request
626        user = john
627        request.POST['old_password'] = 's3cr3t'
628        request.POST['password'] = 'h1dd3n s3cr3t'
629        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
630        response = user_views.change_password(user, request)
631        assert isinstance(response, HTTPFound)
632        assert response.location == request.resource_url(user, 'profile')
633        # password was changed
634        assert not user.check_password('s3cr3t')
635        assert user.check_password('h1dd3n s3cr3t')
636
637    def test_change_password_post_no_values(self, passwd_post_request, john):
638        request = passwd_post_request
639        user = john
640        response = user_views.change_password(user, request)
641        assert isinstance(response['form'], OWFormRenderer)
642        error = u'Please enter a value'
643        html_error = u'<ul class="error">'
644        html_error += ('<li>' + error + '</li>') * 3  # 3 fields
645        html_error += '</ul>'
646        errorlist = response['form'].errorlist().replace('\n', '')
647        assert errorlist == html_error
648        assert response['form'].errors_for('old_password') == [error]
649        assert response['form'].errors_for('password') == [error]
650        assert response['form'].errors_for('password_confirm') == [error]
651        # password was not changed
652        assert user.check_password('s3cr3t')
653
654    def test_change_password_post_bad_old_password(
655            self, passwd_post_request, john):
656        request = passwd_post_request
657        user = john
658        request.POST['old_password'] = 'FAIL PASSWORD'
659        request.POST['password'] = 'h1dd3n s3cr3t'
660        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
661        response = user_views.change_password(user, request)
662        assert isinstance(response['form'], OWFormRenderer)
663        error = u'The given password does not match the existing one '
664        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
665        assert response['form'].errorlist() == html_error
666        assert response['form'].errors_for('old_password') == [error]
667        # password was not changed
668        assert user.check_password('s3cr3t')
669        assert not user.check_password('h1dd3n s3cr3t')
670
671    def test_change_password_post_password_mismatch(
672            self, passwd_post_request, john):
673        request = passwd_post_request
674        user = john
675        request.POST['old_password'] = 's3cr3t'
676        request.POST['password'] = 'h1dd3n s3cr3ts'
677        request.POST['password_confirm'] = 'h1dd3n s3cr3t'
678        response = user_views.change_password(user, request)
679        assert isinstance(response['form'], OWFormRenderer)
680        error = u'Fields do not match'
681        html_error = u'<ul class="error"><li>' + error + '</li></ul>'
682        assert response['form'].errorlist() == html_error
683        assert response['form'].errors_for('password_confirm') == [error]
684        # password was not changed
685        assert user.check_password('s3cr3t')
686        assert not user.check_password('h1dd3n s3cr3t')
687
688    def test_signup_get(self, dummy_request):
689        request = dummy_request
690        response = user_views.signup(request.root, request)
691        assert isinstance(response['form'], OWFormRenderer)
692        # no errors in the form (first load)
693        assert response['form'].errorlist() == ''
694
695    @patch('ow.views.user.send_verification_email')
696    def test_signup_post_ok(self, sve, signup_post_request):
697        request = signup_post_request
698        assert 'jack.black@example.net' not in request.root.emails
699        assert 'JackBlack' not in request.root.all_nicknames
700        response = user_views.signup(request.root, request)
701        assert isinstance(response, HTTPFound)
702        assert response.location == request.resource_url(request.root)
703        assert 'jack.black@example.net' in request.root.emails
704        assert 'JackBlack' in request.root.all_nicknames
705        # user is in "unverified" state
706        user = request.root.get_user_by_email('jack.black@example.net')
707        assert not user.verified
708        assert isinstance(user.verification_token, UUID)
709        # also, we sent an email to that user
710        sve.assert_called_once_with(request, user)
711
712    def test_signup_missing_required(self, signup_post_request):
713        request = signup_post_request
714        request.POST['email'] = ''
715        assert 'jack.black@example.net' not in request.root.emails
716        assert 'JackBlack' not in request.root.all_nicknames
717        response = user_views.signup(request.root, request)
718        assert isinstance(response['form'], OWFormRenderer)
719        error = u'Please enter an email address'
720        html_error = '<ul class="error">'
721        html_error += '<li>' + error + '</li>'
722        html_error += '</ul>'
723        errorlist = response['form'].errorlist().replace('\n', '')
724        assert errorlist == html_error
725        assert response['form'].errors_for('email') == [error]
726        assert 'jack.black@example.net' not in request.root.emails
727        assert 'JackBlack' not in request.root.all_nicknames
728
729    def test_signup_existing_nickname(self, signup_post_request, john):
730        request = signup_post_request
731        # assign john a nickname first
732        john.nickname = 'john'
733        # now set it for the POST request
734        request.POST['nickname'] = 'john'
735        # check jack is not there yet
736        assert 'jack.black@example.net' not in request.root.emails
737        assert 'JackBlack' not in request.root.all_nicknames
738        # now signup as jack, but trying to set the nickname 'john'
739        response = user_views.signup(request.root, request)
740        assert isinstance(response['form'], OWFormRenderer)
741        error = u'Another user is already using the nickname john'
742        html_error = '<ul class="error">'
743        html_error += '<li>' + error + '</li>'
744        html_error += '</ul>'
745        errorlist = response['form'].errorlist().replace('\n', '')
746        assert errorlist == html_error
747        assert response['form'].errors_for('nickname') == [error]
748        # all the errors, and jack is not there
749        assert 'jack.black@example.net' not in request.root.emails
750        assert 'JackBlack' not in request.root.all_nicknames
751
752    def test_signup_existing_email(self, signup_post_request):
753        request = signup_post_request
754        request.POST['email'] = 'john.doe@example.net'
755        assert 'jack.black@example.net' not in request.root.emails
756        assert 'JackBlack' not in request.root.all_nicknames
757        response = user_views.signup(request.root, request)
758        assert isinstance(response['form'], OWFormRenderer)
759        error = u'Another user is already registered with the email '
760        error += u'john.doe@example.net'
761        html_error = '<ul class="error">'
762        html_error += '<li>' + error + '</li>'
763        html_error += '</ul>'
764        errorlist = response['form'].errorlist().replace('\n', '')
765        assert errorlist == html_error
766        assert response['form'].errors_for('email') == [error]
767        assert 'jack.black@example.net' not in request.root.emails
768        assert 'JackBlack' not in request.root.all_nicknames
769
770    def test_week_stats_no_stats(self, dummy_request, john):
771        response = user_views.week_stats(john, dummy_request)
772        assert isinstance(response, Response)
773        assert response.content_type == 'application/json'
774        # the body is a valid json-encoded stream
775        obj = json.loads(response.body)
776        assert obj == [
777            {'distance': 0, 'elevation': 0, 'name': 'Mon',
778             'time': '00', 'workouts': 0},
779            {'distance': 0, 'elevation': 0, 'name': 'Tue',
780             'time': '00', 'workouts': 0},
781            {'distance': 0, 'elevation': 0, 'name': 'Wed',
782             'time': '00', 'workouts': 0},
783            {'distance': 0, 'elevation': 0, 'name': 'Thu',
784             'time': '00', 'workouts': 0},
785            {'distance': 0, 'elevation': 0, 'name': 'Fri',
786             'time': '00', 'workouts': 0},
787            {'distance': 0, 'elevation': 0, 'name': 'Sat',
788             'time': '00', 'workouts': 0},
789            {'distance': 0, 'elevation': 0, 'name': 'Sun',
790             'time': '00', 'workouts': 0}
791        ]
792
793    def test_week_stats(self, dummy_request, john):
794        workout = Workout(
795            start=datetime.now(timezone.utc),
796            duration=timedelta(minutes=60),
797            distance=30,
798            elevation=540
799        )
800        john.add_workout(workout)
801        response = user_views.week_stats(john, dummy_request)
802        assert isinstance(response, Response)
803        assert response.content_type == 'application/json'
804        # the body is a valid json-encoded stream
805        obj = json.loads(response.body)
806        assert len(obj) == 7
807        for day in obj:
808            if datetime.now(timezone.utc).strftime('%a') == day['name']:
809                day['distance'] == 30
810                day['elevation'] == 540
811                day['time'] == '01'
812                day['workouts'] == 1
813            else:
814                day['distance'] == 0
815                day['elevation'] == 0
816                day['time'] == '00'
817                day['workouts'] == 0
Note: See TracBrowser for help on using the repository browser.