From 037dcf05c1c7b9bd83b6709f71726ae9fcc55b36 Mon Sep 17 00:00:00 2001 From: Durval Carvalho Date: Fri, 27 Sep 2019 17:05:42 -0300 Subject: [PATCH 01/14] #43 Add Integration Tests --- src/acacia/urls.py | 6 +-- src/users/serializers.py | 2 +- src/users/tests.py | 107 ++++++++++++++++++++++++++++++++++++++- src/users/urls.py | 2 +- 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/src/acacia/urls.py b/src/acacia/urls.py index 987f12f..240f371 100644 --- a/src/acacia/urls.py +++ b/src/acacia/urls.py @@ -16,11 +16,7 @@ from django.contrib import admin from django.urls import path, include -api_urls = [ - path('users/', include('users.urls')), -] - urlpatterns = [ path('admin/', admin.site.urls), - path('api/', include(api_urls)), + path('users/', include('users.urls')), ] \ No newline at end of file diff --git a/src/users/serializers.py b/src/users/serializers.py index 9532e0d..1dd0a14 100644 --- a/src/users/serializers.py +++ b/src/users/serializers.py @@ -27,7 +27,7 @@ class Meta: model = User fields = ['username', 'email', 'password', 'confirm_password'] - def validade_email(self, email): + def validate_email(self, email): if User.objects.filter(email=email).exists(): raise serializers.ValidationError('Email already exists.') return email diff --git a/src/users/tests.py b/src/users/tests.py index 7ce503c..66e06a7 100644 --- a/src/users/tests.py +++ b/src/users/tests.py @@ -1,3 +1,106 @@ -from django.test import TestCase +import json -# Create your tests here. +from rest_framework.test import APITestCase +from django.urls import reverse + + +class UserRegistrationAPIViewTestCase(APITestCase): + url = reverse('users:signup') + + def test_different_password_on_password_confirmation(self): + """ + Test to try to create a user with a wrong verification password + """ + + user_data = { + "username": "vitas", + "email": "vitas@iAmGreat.com", + "password": "VitasIsNice", + "confirm_password": "VitasIsAwesome" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(400, response.status_code) + + + def test_password_less_than_8_characters(self): + """ + Test to try to create a user with a password less than 8 characters + """ + + user_data = { + "username": "vitas", + "email": "vitas@iAmGreat.com", + "password": "HiVitas", + "confirm_password": "HiVitas" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(400, response.status_code) + + + def test_unique_email_validation(self): + """ + Test to try to create a user with a registered email + """ + + user_data = { + "username": "vitas", + "email": "vitas@iAmGreat.com", + "password": "VitasIsAwesome", + "confirm_password": "VitasIsAwesome" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(201, response.status_code) + + user_data = { + "username": "Reanu_Reves", + "email": "vitas@iAmGreat.com", + "password": "cyberpunk2077", + "confirm_password": "cyberpunk2077" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(400, response.status_code) + + def test_unique_username_validation(self): + """ + Test to try to create a user with a registered username + """ + + user_data = { + "username": "vitas", + "email": "vitas@iAmGreat.com", + "password": "VitasIsAwesome", + "confirm_password": "VitasIsAwesome" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(201, response.status_code) + + user_data = { + "username": "vitas", + "email": "keanu@reeves.com", + "password": "cyberpunk2077", + "confirm_password": "cyberpunk2077" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(400, response.status_code) + + + def test_user_registration(self): + """ + Test to create a user with valid data + """ + + user_data = { + "username": "keanu_reeves", + "email": "keanu@reeves.com", + "password": "cyberpunk2077", + "confirm_password": "cyberpunk2077" + } + + response = self.client.post(self.url, user_data) + self.assertEqual(201, response.status_code) \ No newline at end of file diff --git a/src/users/urls.py b/src/users/urls.py index 3f1501c..a739968 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -6,5 +6,5 @@ app_name = 'users' urlpatterns = [ - path('register/', UserRegistrationAPIView.as_view(), name='register'), + path('signup/', UserRegistrationAPIView.as_view(), name='signup'), ] \ No newline at end of file From a955c12eeb7e61d1c8eee27a6f08318595541573 Mon Sep 17 00:00:00 2001 From: Leonardo da Silva Gomes Date: Sun, 29 Sep 2019 20:01:48 -0300 Subject: [PATCH 02/14] #43 Add cors django application --- requirements.txt | 1 + src/acacia/settings.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 65a0f35..d8db0da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Django==2.2.4 djangorestframework==3.10.0 psycopg2==2.8.3 +django-cors-headers==3.1.0 django-phonenumber-field phonenumbers \ No newline at end of file diff --git a/src/acacia/settings.py b/src/acacia/settings.py index 12bec10..85df66c 100644 --- a/src/acacia/settings.py +++ b/src/acacia/settings.py @@ -43,6 +43,7 @@ # libs 'rest_framework', 'phonenumber_field', + 'corsheaders', # my apps 'users', @@ -56,6 +57,8 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', ] ROOT_URLCONF = 'acacia.urls' @@ -141,4 +144,11 @@ 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', ] -} \ No newline at end of file +} + +# CORS headers to responses + +CORS_ORIGIN_WHITELIST = [ + "http://localhost:8080", + "http://localhost:8000", +] \ No newline at end of file From 84147f8246d2b5b0f1e970d0544db39a9dc89ea5 Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Mon, 30 Sep 2019 18:53:04 -0300 Subject: [PATCH 03/14] #43 Translate user serializer errors messages Co-authored-by: Leonardo da Silva Gomes --- src/users/serializers.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/users/serializers.py b/src/users/serializers.py index 1dd0a14..e094c2f 100644 --- a/src/users/serializers.py +++ b/src/users/serializers.py @@ -19,8 +19,8 @@ class UserRegistrationSerializer(serializers.ModelSerializer): confirm_password = serializers.CharField( write_only=True, required=True, - label="Confirm Password", - style={'input_type': 'password'} + label="Confirm Password", + style={'input_type': 'password'} ) class Meta: @@ -29,7 +29,7 @@ class Meta: def validate_email(self, email): if User.objects.filter(email=email).exists(): - raise serializers.ValidationError('Email already exists.') + raise serializers.ValidationError('Email já cadastrado') return email @@ -37,7 +37,7 @@ def validate_password(self, password): min_length = getattr(settings, 'PASSWORD_MIN_LENGTH', 8) if len(password) < min_length: raise serializers.ValidationError( - 'Password should be atleast %s characters long.' % (min_length) + 'A senha deve ter no mínimo %s caracteres' % (min_length) ) return password @@ -45,12 +45,13 @@ def validate_confirm_password(self, password_confirmation): data = self.get_initial() password = data.get('password') if password != password_confirmation: - raise serializers.ValidationError('Passwords must match.') + raise serializers.ValidationError('As senhas devem corresponder') return password_confirmation def validate_username(self, username): + username = property(lambda self: self.name or self.email.replace(" ", "_")) if User.objects.filter(username=username).exists(): - raise serializers.ValidationError('Email already exists.') + raise serializers.ValidationError('Usuário com este nome já cadastrado') return username def create(self, validated_data): From c2e71a5805683027388afb05f7665aa317143a7d Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Mon, 30 Sep 2019 22:19:19 -0300 Subject: [PATCH 04/14] #43 Add tokens lib Co-authored-by: Leonardo da Silva Gomes --- src/users/urls.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/users/urls.py b/src/users/urls.py index a739968..1054fcd 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -1,10 +1,19 @@ -from django.urls import path +# Django +from django.urls import path, include + +# Viewsets from .viewsets import UserRegistrationAPIView -from rest_framework.authtoken.views import obtain_auth_token +# Simple JWT +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, +) app_name = 'users' urlpatterns = [ path('signup/', UserRegistrationAPIView.as_view(), name='signup'), + path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] \ No newline at end of file From 5f5daf1edc6ab3cfb4dec7b30c57425c734c5f1d Mon Sep 17 00:00:00 2001 From: Leonardo da Silva Gomes Date: Tue, 1 Oct 2019 08:31:18 -0300 Subject: [PATCH 05/14] Add Token configurations --- requirements.txt | 1 + src/acacia/settings.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index d8db0da..f7a24eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,6 @@ Django==2.2.4 djangorestframework==3.10.0 psycopg2==2.8.3 django-cors-headers==3.1.0 +djangorestframework-simplejwt django-phonenumber-field phonenumbers \ No newline at end of file diff --git a/src/acacia/settings.py b/src/acacia/settings.py index 85df66c..ad7fd05 100644 --- a/src/acacia/settings.py +++ b/src/acacia/settings.py @@ -41,7 +41,7 @@ 'django.contrib.staticfiles', # libs - 'rest_framework', + 'rest_framework.authtoken', 'phonenumber_field', 'corsheaders', @@ -142,13 +142,13 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', ] } # CORS headers to responses CORS_ORIGIN_WHITELIST = [ - "http://localhost:8080", - "http://localhost:8000", + "http://0.0.0.0:8080", + "http://0.0.0.0:8000", ] \ No newline at end of file From 740253b72c7c522cede7d9434ea792d33cbbf7c9 Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Tue, 1 Oct 2019 17:10:30 -0300 Subject: [PATCH 06/14] #43 Fix merge conflicts Co-authored-by: Leonardo da Silva Gomes --- src/users/urls.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/users/urls.py b/src/users/urls.py index d5dae87..1054fcd 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -13,11 +13,7 @@ app_name = 'users' urlpatterns = [ -<<<<<<< HEAD path('signup/', UserRegistrationAPIView.as_view(), name='signup'), -======= - path('signup/', UserRegistrationAPIView.as_view(), name='register'), ->>>>>>> f31e1452b216abcc05a194a3985b768a7eff4b1e path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] \ No newline at end of file From 7a1b6d75ec5bb6acb44e11e6cdabe86eae6ae37e Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Tue, 1 Oct 2019 18:56:17 -0300 Subject: [PATCH 07/14] #43 Fix URL errors Co-authored-by: Leonardo da Silva Gomes --- src/users/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/users/urls.py b/src/users/urls.py index 1054fcd..d7eb35b 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -13,7 +13,7 @@ app_name = 'users' urlpatterns = [ - path('signup/', UserRegistrationAPIView.as_view(), name='signup'), + path('signup/', UserRegistrationAPIView.as_view(), name='register'), path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] \ No newline at end of file From c08167f59d370d92f5f21648a0a91f3469701396 Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Tue, 1 Oct 2019 18:56:17 -0300 Subject: [PATCH 08/14] #43 Fix URL errors Co-authored-by: Leonardo da Silva Gomes --- src/users/tests.py | 2 +- src/users/urls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/users/tests.py b/src/users/tests.py index 6256b5c..0500974 100644 --- a/src/users/tests.py +++ b/src/users/tests.py @@ -5,7 +5,7 @@ class UserRegistrationAPIViewTestCase(APITestCase): - url = reverse('users:signup') + url = reverse('users:register') def test_different_password_on_password_confirmation(self): """ diff --git a/src/users/urls.py b/src/users/urls.py index 1054fcd..d7eb35b 100644 --- a/src/users/urls.py +++ b/src/users/urls.py @@ -13,7 +13,7 @@ app_name = 'users' urlpatterns = [ - path('signup/', UserRegistrationAPIView.as_view(), name='signup'), + path('signup/', UserRegistrationAPIView.as_view(), name='register'), path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ] \ No newline at end of file From 2b4288f64bb555fe773cb3aed0817b7ad019db86 Mon Sep 17 00:00:00 2001 From: Hugo Sobral Date: Tue, 1 Oct 2019 20:25:05 -0300 Subject: [PATCH 09/14] #43 update 'email' attributes in users serializer Co-authored-by: Leonardo da Silva Gomes --- src/users/serializers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/users/serializers.py b/src/users/serializers.py index bedf65f..1e21eb4 100644 --- a/src/users/serializers.py +++ b/src/users/serializers.py @@ -19,6 +19,7 @@ class UserSignUpSerializer(serializers.Serializer): email = serializers.EmailField( required=True, + unique=True, label="Email Address", ) @@ -40,10 +41,10 @@ class Meta: model = User fields = ['username', 'email', 'password', 'confirm_password'] - def validate_email(self, email): - if User.objects.filter(email=email).exists(): - raise serializers.ValidationError('Email já cadastrado') - return email + #def validate_email(self, email): + # if User.objects.filter(email=email).exists(): + # raise serializers.ValidationError('Email já cadastrado') + # return email def validate_password(self, password): From 9b448fac87f39531ea84615a2290ce3432c7c5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fab=C3=ADola=20Fleury?= Date: Tue, 1 Oct 2019 22:28:46 -0300 Subject: [PATCH 10/14] #43 Try to fix database connection trouble --- src/acacia/settings.py | 3 +++ src/acacia/wait_db.py | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/acacia/wait_db.py diff --git a/src/acacia/settings.py b/src/acacia/settings.py index 68c828b..579560b 100644 --- a/src/acacia/settings.py +++ b/src/acacia/settings.py @@ -14,6 +14,8 @@ from scripts.wait_for_db import start_services from django.utils.translation import ugettext_lazy as _ +from .wait_db import start_services + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -93,6 +95,7 @@ } } +# STARTS SERVICES THAT DJANGO DEPENDS E.G. postgres start_services() LANGUAGES = ( diff --git a/src/acacia/wait_db.py b/src/acacia/wait_db.py new file mode 100644 index 0000000..fdf9b2e --- /dev/null +++ b/src/acacia/wait_db.py @@ -0,0 +1,56 @@ +import importlib +import os +import time + +import logging + +SERVICES_STARTED = False + +log = logging.getLogger('ej') + + +def start_services(): + global SERVICES_STARTED + + if SERVICES_STARTED: + return + + start_postgres() + + SERVICES_STARTED = True + + +def start_postgres(): + # settings_path = os.environ['DJANGO_SETTINGS_MODULE'] + # settings = importlib.import_module(settings_path) + + # db = settings.DATABASES['default'] + dbname = 'postgres' # db['NAME'] + user = 'postgres' # db['USER'] + password = '' # db['PASSWORD'] + host = 'db' # db['HOST'] + + for _ in range(100): + if can_connect(dbname, user, password, host): + log.info("Postgres is available. Continuing...") + return + log.warning('Postgres is unavailable. Retrying in 0.5 seconds') + time.sleep(0.5) + + log.critical('Maximum number of attempts connecting to postgres database') + raise RuntimeError('could not connect to database') + + +def can_connect(dbname, user, password, host): + import psycopg2 + + try: + psycopg2.connect( + dbname=dbname, + user=user, + password=password, + host=host + ) + except psycopg2.OperationalError: + return False + return True From 65fd69f2893d74f3c3c6aeca3d86489790808fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fab=C3=ADola=20Fleury?= Date: Tue, 1 Oct 2019 23:36:12 -0300 Subject: [PATCH 11/14] #43 Map postgres volume in docker --- docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 840bbb7..20f698e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: db: image: postgres + volumes: + - postgres_data:/var/lib/postgresql/data acacia-back: container_name: acacia_backend @@ -25,4 +27,7 @@ services: volumes: - .:/code depends_on: - - db \ No newline at end of file + - db + +volumes: + postgres_data: From 1e447cb38277d69f7f1416f61360a4640052acc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fab=C3=ADola=20Fleury?= Date: Wed, 2 Oct 2019 09:14:39 -0300 Subject: [PATCH 12/14] #43 Adjust django version to support validation --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0f084cb..4e4ab77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==2.2 +Django==2.2.4 djangorestframework==3.10 django-phonenumber-field==3.0 phonenumbers==8.10 From 4e1b4501cd2cccf2f0d1cf13665c448bdf13d070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Xoteslem?= Date: Wed, 2 Oct 2019 13:07:52 -0300 Subject: [PATCH 13/14] #43 Use UniqueValidator in serializer --- src/users/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/users/serializers.py b/src/users/serializers.py index 1e21eb4..ac78c53 100644 --- a/src/users/serializers.py +++ b/src/users/serializers.py @@ -19,7 +19,8 @@ class UserSignUpSerializer(serializers.Serializer): email = serializers.EmailField( required=True, - unique=True, + validators=[UniqueValidator(queryset=User.objects.all())], + #unique=True, label="Email Address", ) From 65280283e3eda4cfa797607ff119d1018a0f97d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Xoteslem?= Date: Wed, 2 Oct 2019 13:09:08 -0300 Subject: [PATCH 14/14] #43 Assert test db integrityError --- src/users/tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/users/tests.py b/src/users/tests.py index 0500974..dff5b7b 100644 --- a/src/users/tests.py +++ b/src/users/tests.py @@ -2,6 +2,7 @@ from rest_framework.test import APITestCase from django.urls import reverse +from django.db import IntegrityError class UserRegistrationAPIViewTestCase(APITestCase): @@ -44,25 +45,23 @@ def test_unique_email_validation(self): Test to try to create a user with a registered email """ - user_data = { + user1_data = { "username": "vitas", "email": "vitas@iAmGreat.com", "password": "VitasIsAwesome", "confirm_password": "VitasIsAwesome" } - - response = self.client.post(self.url, user_data) - self.assertEqual(201, response.status_code) - - user_data = { + user2_data = { "username": "Reanu_Reves", "email": "vitas@iAmGreat.com", "password": "cyberpunk2077", "confirm_password": "cyberpunk2077" } - - response = self.client.post(self.url, user_data) - self.assertEqual(400, response.status_code) + response = self.client.post(self.url, user1_data) + self.assertEqual(201, response.status_code) + with self.assertRaises(IntegrityError): + response = self.client.post(self.url, user2_data) + def test_unique_username_validation(self): """