diff --git a/backend_challenge/content b/backend_challenge/content new file mode 100644 index 0000000..67c2810 --- /dev/null +++ b/backend_challenge/content @@ -0,0 +1,14 @@ +https://github.com/vitorfs/bootcamp/blob/master/env.example +https://github.com/vitorfs/bootcamp/blob/master/.travis.yml +https://simpleisbetterthancomplex.com/2015/11/26/package-of-the-week-python-decouple.html +https://docs.djangoproject.com/en/3.1/topics/security/ +https://github.com/vitorfs/parsifal/blob/master/parsifal/settings.py +ALLOWED_HOSTS +DB_HOST +DB_NAME +DB_PASSWORD +DB_USER +DEBUG +OIDC_RP_CLIENT_ID +OIDC_RP_CLIENT_SECRET +SECRET_KEY \ No newline at end of file diff --git a/backend_challenge/core/.coveragerc b/backend_challenge/core/.coveragerc new file mode 100644 index 0000000..261a584 --- /dev/null +++ b/backend_challenge/core/.coveragerc @@ -0,0 +1,27 @@ +# .coveragerc to control coverage.py +[run] +source = . +omit = + *venv/* + *venv/bin/* + *venv/include/* + *venv/lib/* + *manage.py + */settings.py + */local_settings.py + */tests/* + *apps.py + *migrations/* + *asgi.py + *wsgi.py + + *__init__* + */__pycache__/* + */site-packages/* + */distutils/* + + +[report] +#fail_under = 100 +show_missing = True +#skip_covered = True \ No newline at end of file diff --git a/backend_challenge/core/migrations/0001_initial.py b/backend_challenge/core/migrations/0001_initial.py index e085c4a..c7e8b0a 100644 --- a/backend_challenge/core/migrations/0001_initial.py +++ b/backend_challenge/core/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 3.1.1 on 2020-10-05 04:24 +# Generated by Django 3.1.1 on 2021-03-05 22:48 from django.conf import settings +import django.core.validators from django.db import migrations, models import django.db.models.deletion @@ -14,14 +15,30 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('added', models.DateTimeField(auto_now_add=True)), + ('edited', models.DateTimeField(auto_now=True)), + ('phone', models.CharField(max_length=13, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format '+254234567892'. Up to 12 digits allowed with no", regex='^\\+254\\d{9}$')])), + ('code', models.CharField(max_length=15, verbose_name='code')), + ('code2', models.CharField(max_length=15, verbose_name='code2')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='Order', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('added', models.DateTimeField(auto_now_add=True)), ('edited', models.DateTimeField(auto_now=True)), - ('item', models.CharField(max_length=200)), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('item', models.CharField(max_length=200, verbose_name='item')), + ('amount', models.PositiveIntegerField()), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='core.customer')), ], options={ 'ordering': ['-added'], diff --git a/backend_challenge/core/migrations/0002_auto_20201015_0515.py b/backend_challenge/core/migrations/0002_auto_20201015_0515.py deleted file mode 100644 index 25ed1e1..0000000 --- a/backend_challenge/core/migrations/0002_auto_20201015_0515.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-15 05:15 - -from django.db import migrations, models -import django.db.models.deletion -import mozilla_django_oidc.auth - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Customer', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('phone', models.CharField(blank=True, default='', max_length=12, verbose_name='phone')), - ], - bases=(mozilla_django_oidc.auth.OIDCAuthenticationBackend, models.Model), - ), - migrations.AddField( - model_name='order', - name='amount', - field=models.PositiveIntegerField(default=0), - ), - migrations.AlterField( - model_name='order', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.customer'), - ), - ] diff --git a/backend_challenge/core/migrations/0002_remove_customer_code2.py b/backend_challenge/core/migrations/0002_remove_customer_code2.py new file mode 100644 index 0000000..9f477fa --- /dev/null +++ b/backend_challenge/core/migrations/0002_remove_customer_code2.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.1 on 2021-03-05 23:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='customer', + name='code2', + ), + ] diff --git a/backend_challenge/core/migrations/0003_customer_user.py b/backend_challenge/core/migrations/0003_customer_user.py deleted file mode 100644 index 1a0a88e..0000000 --- a/backend_challenge/core/migrations/0003_customer_user.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-21 22:49 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('core', '0002_auto_20201015_0515'), - ] - - operations = [ - migrations.AddField( - model_name='customer', - name='user', - field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to='auth.user'), - preserve_default=False, - ), - ] diff --git a/backend_challenge/core/migrations/0004_auto_20201028_2244.py b/backend_challenge/core/migrations/0004_auto_20201028_2244.py deleted file mode 100644 index 727fb21..0000000 --- a/backend_challenge/core/migrations/0004_auto_20201028_2244.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-28 22:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0003_customer_user'), - ] - - operations = [ - migrations.AlterField( - model_name='customer', - name='phone', - field=models.CharField(max_length=12, verbose_name='phone'), - ), - ] diff --git a/backend_challenge/core/migrations/0005_auto_20201028_2315.py b/backend_challenge/core/migrations/0005_auto_20201028_2315.py deleted file mode 100644 index dd7f336..0000000 --- a/backend_challenge/core/migrations/0005_auto_20201028_2315.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-28 23:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0004_auto_20201028_2244'), - ] - - operations = [ - migrations.AlterField( - model_name='order', - name='amount', - field=models.PositiveIntegerField(default=1), - ), - ] diff --git a/backend_challenge/core/models.py b/backend_challenge/core/models.py index 300af46..a257ede 100644 --- a/backend_challenge/core/models.py +++ b/backend_challenge/core/models.py @@ -1,10 +1,29 @@ from django.db import models from mozilla_django_oidc.auth import OIDCAuthenticationBackend from django.contrib.auth.models import User +from django.core.validators import RegexValidator -class Customer(models.Model): - phone = models.CharField('phone', max_length=12) + +class TimeStampedModel(models.Model): + added = models.DateTimeField(auto_now_add=True) + edited = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + +class Customer(TimeStampedModel): + phone= models.CharField(max_length=13, + validators=[ + RegexValidator( + regex=r'^\+254\d{9}$', + message="Phone number must be entered in the format '+254234567892'. Up to 12 digits allowed with no" + ), + ], + ) user = models.OneToOneField(User, on_delete=models.CASCADE) + code= models.CharField('code', max_length=15) + + # validationhttps://stackoverflow.com/questions/19130942/whats-the-best-way-to-store-phone-number-in-django-models def __str__(self): return self.user.email @@ -12,18 +31,13 @@ def __str__(self): -class TimeStampedModel(models.Model): - added = models.DateTimeField(auto_now_add=True) - edited = models.DateTimeField(auto_now=True) - class Meta: - abstract = True class Order(TimeStampedModel): - item = models.CharField(max_length=200, ) - amount = models.PositiveIntegerField(default=1) - customer = models.ForeignKey('Customer', on_delete=models.CASCADE) + item = models.CharField('item',max_length=200 ) + amount = models.PositiveIntegerField() + customer = models.ForeignKey('Customer',related_name='orders',on_delete=models.CASCADE) def __str__(self): return str(self.id) diff --git a/backend_challenge/core/movie.py b/backend_challenge/core/movie.py new file mode 100644 index 0000000..48c8707 --- /dev/null +++ b/backend_challenge/core/movie.py @@ -0,0 +1,42 @@ +from hypothesis.extra.django import TestCase +import pytest +from hypothesis import strategies as st, given +from django.contrib.auth.models import User +from .models import Order, Customer +from mixer.backend.django import mixer +@pytest.fixture +def api_client(): + from rest_framework.test import APIClient + return APIClient() + +@pytest.mark.django_db +def test_user_create(): + User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword') + assert User.objects.count() == 1 + + + +@pytest.mark.django_db +def test_customer_model(): + user=User.objects.create(username="eugine",email="ochungeugine@gmail.com") + customer= Customer.objects.create(user=user,phone=733333) + assert Customer.objects.count() == 1 +@pytest.mark.django_db +def test_string_representation(): + user=User.objects.create(username="eugine",email="ochungeugine@gmail.com") + customer= Customer.objects.create(user=user,phone=733333) + assert str(customer)=="ochungeugine@gmail.com" + +@pytest.mark.django_db +def test_authenticated_user_can_create_new_order(api_client): + + data= { + 'item': 'books', + 'amount': 4, + 'customer': self.customer.id + } + response=self.client.post(self.url, data=data) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Order.objects.count(), 1) + \ No newline at end of file diff --git a/backend_challenge/core/serializers.py b/backend_challenge/core/serializers.py index bdcadf1..5770c37 100644 --- a/backend_challenge/core/serializers.py +++ b/backend_challenge/core/serializers.py @@ -1,22 +1,39 @@ from rest_framework import serializers -from .models import Order,Customer +from .models import Order, Customer +from django.contrib.auth.models import User +from rest_framework.exceptions import ValidationError + class CustomerSerializer(serializers.ModelSerializer): - class Meta: + + class Meta: model = Customer - fields = ("phone",) + fields = ("phone", "code") + + + class OrderSerializer(serializers.ModelSerializer): - customer_phone = serializers.SerializerMethodField() - send_sms=serializers.SerializerMethodField() + customer = CustomerSerializer(read_only=True) class Meta: model = Order - fields = ("item", "amount","customer_phone","send_sms") + fields = ("item", "amount","customer") + def validate(self, data): + if data.get('customer') is None: + raise serializers.ValidationError( + 'Please Update your profile details' + ) - def get_customer_phone(self, obj): - return obj.customer.phone + return data - def get_send_sms(self, obj): - message = f'Dear {obj.customer.user} You have successfully placed an order.Your order ID is {obj.id}.An SMS was sent confirming receipt of your order' - return message + + def create(self, validated_data): + request = self.context.get('request', None) + if request is None: + return False + + customer=request.user.customer + print(customer) + '''Create a new Customer instance, given the accepted data.''' + return Order.objects.create(**validated_data,customer=customer) diff --git a/backend_challenge/core/tasks.py b/backend_challenge/core/tasks.py index 60410fd..a2555f9 100644 --- a/backend_challenge/core/tasks.py +++ b/backend_challenge/core/tasks.py @@ -1,8 +1,9 @@ +from django.conf import settings from celery import shared_task import africastalking from.models import Order username = "sandbox" # use 'sandbox' for development in the test environment -api_key = "c24b10b049468747684e01f846e1a7420e106584c144d187b62d39fe667b6a78" +api_key = settings.AFRICASTALKING_API_KEY africastalking.initialize(username, api_key) sms = africastalking.SMS @@ -13,10 +14,10 @@ def send_sms(order_id): Task to send an sms notification when an order is successfully created. """ - order = Order.objects.get(id=order_id) + order = Order.objects.select_related('customer').get(id=order_id) message = f'Dear {order.customer.user} You have successfully placed an order.Your order ID is {order.id}.' - response=sms.send(message,["+254728826517"]) + response=sms.send(message,[order.customer.phone]) return response # sudo service redis-server stop # $ celery -A backend_challenge worker -l INFO diff --git a/backend_challenge/core/test_models.py b/backend_challenge/core/test_models.py new file mode 100644 index 0000000..92bfee3 --- /dev/null +++ b/backend_challenge/core/test_models.py @@ -0,0 +1,34 @@ + +from django.test import TestCase +from django.contrib.auth.models import User +from .models import Order, Customer +from rest_framework import status +from django.urls import reverse +from .import tasks +from .tasks import * +from rest_framework.test import APITestCase + + +class TestCustomer(APITestCase): + """ class to test the profile models""" + + def setUp(self): + """ Setup some code that is used by the unittests""" + self.email = 'serem@gmail.com' + self.username = 'testing' + self.password = 'jcbsdhcvshucj!!' + + # create a user that will be logged in + self.user = User.objects.create_user( + self.username, self.email, self.password) + self.customer=Customer.objects.create(user=self.user,phone='0728826517') + + + def test_customer_creation(self): + self.assertEqual(self.customer.__str__(), + "serem@gmail.com" + ) + self.assertTrue(isinstance(self.customer,Customer)) + + + \ No newline at end of file diff --git a/backend_challenge/core/tests.py b/backend_challenge/core/tests.py index 58d9db9..81135d9 100644 --- a/backend_challenge/core/tests.py +++ b/backend_challenge/core/tests.py @@ -1,130 +1,166 @@ import json from django.test import TestCase -from rest_framework.test import APITestCase from django.contrib.auth.models import User from .models import Order, Customer from rest_framework import status from django.urls import reverse from .import tasks from .tasks import * +from rest_framework.test import APITestCase # class OrderModelTest(TestCase): # def test_string_representation(self): # order= Order(id=2) # self.assertEqual(str(order), "2" ) +# @pytest.mark.django_db +# def test_user_create(): +# User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword') +# assert User.objects.count() == 1 - - -class TestOrderAPI(APITestCase): - url = '/api/v1/order' +class TestCreate_Customer(APITestCase): + url = '/api/v1/customer' def setUp(self): self.username = "eugine" + self.username1 = "egine" + self.email='ochungeugine@gmil.com' self.user = User.objects.create_user(self.username) + self.user2 = User.objects.create_user(self.username1) self.client.force_authenticate(user=self.user) - self.customer=Customer.objects.create(user=self.user,phone="0728825517") - + + - def test_authenticated_user_can_create_new_order(self): - + def test_authenticated_user_can_create_customer(self): data= { - 'item': 'books', - 'amount': 4, - 'customer': self.customer.id + 'user':self.user, + 'phone': '+254728826517', + 'code': '0728826517' } - response=self.client.post(self.url, data=data) + + response=self.client.post(self.url,data=data) print(response.data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Order.objects.count(), 1) - # self.assertEqual(str(order), Order.id ) - + self.assertEqual(Customer.objects.count(), 1) + - def test_anonymous_user_cannot_create_order(self): + def test_anonymous_user_cannot_create_customer(self): self.client.force_authenticate(user=None) response=self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def test_create_order_with_no_item(self): + def test_create_crustomer_with_no_phone(self): data= { - 'item':'', - 'amount': 4, - 'customer': self.customer.id + 'user':self.user, + 'phone':'', + 'code': '0728826517' + } response=self.client.post(self.url, data=data) + print(response.data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_send_sms(self): + def test_create_customer_with_no_code(self): data= { - 'item': 'books', - 'amount': 4, - 'customer': self.customer.id + 'user':self.user, + 'code':'', + 'phone': '+254728826517', + } response=self.client.post(self.url, data=data) - message = 'Dear customer You have successfully placed an order.Your order ID is 1.' - res =sms.send(message,["+254728826517"]) - print(res) - recipients = res['SMSMessageData']['Recipients'] - assert len(recipients) == 1 - assert recipients[0]['status'] == 'Success' + print(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + - def test_string_representation(self): - order=Order.objects.create(item="books", amount=4, customer=self.customer) - self.assertEqual(str(order), "3" ) - def test_list_orders(self): - response = self.client.get(self.url) - self.assertTrue(len(json.loads(response.content)) == Order.objects.count()) + - -class TestCreate_Customer(APITestCase): - url = '/api/v1/customer' +class TestOrderAPI(APITestCase): + url = '/api/v1/order' def setUp(self): self.username = "eugine" - self.email='ochungeugine@gmil.com' self.user = User.objects.create_user(self.username) self.client.force_authenticate(user=self.user) + self.customer=Customer.objects.create(user=self.user,phone="+254728826517",code="728826517") + - def test_authenticated_user_can_create_customer(self): + def test_authenticated_user_can_create_new_order(self): + data= { - 'user':self.user, - 'phone': '0728826517' + 'item': 'books', + 'amount': 4, + 'customer': self.customer } - - response=self.client.post(self.url,data=data) + response=self.client.post(self.url, data=data) print(response.data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Customer.objects.count(), 1) - + #self.assertEqual(Order.objects.count(), 1) + # self.assertEqual(str(order), Order.id ) + - def test_anonymous_user_cannot_create_customer(self): + def test_anonymous_user_cannot_create_order(self): self.client.force_authenticate(user=None) response=self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def test_create_crustomer_with_no_phone(self): + def test_create_order_with_no_item(self): data= { - 'user':self.user, - 'phone':'' + 'item':'', + 'amount': 4, + 'customer': self.customer.id } response=self.client.post(self.url, data=data) - print(response.data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) -class CustomerModelTest(TestCase): - + # def test_send_sms(self): + # data= { + # 'item': 'books', + # 'amount': 4, + # 'customer': self.customer.id + # } + # response=self.client.post(self.url, data=data) + # message = 'Dear customer You have successfully placed an order.Your order ID is 1.' + # res =sms.send(message,["+254728826517"]) + # print(res) + # recipients = res['SMSMessageData']['Recipients'] + # assert len(recipients) == 1 + # assert recipients[0]['status'] == 'Success' + def test_string_representation(self): - user=User.objects.create(username="eugine",email="ochungeugine@gmail.com") - customer= Customer.objects.create(user=user) - self.assertEqual(str(customer), user.email) + order=Order.objects.create(item="books", amount=4, customer=self.customer) + self.assertEqual(str(order), "2" ) + + def test_list_orders(self): + response = self.client.get(self.url) + print(response) + self.assertTrue(len(json.loads(response.content)) == Order.objects.count()) + +# @pytest.mark.django_db +# def test_send_new_event_service_called( +# mocker, default_event_data, api_client +# ): +# mock_send_new_event = mocker.patch( +# 'service.ThirdPartyService.send_new_event' +# ) +# response = api_client.post( +# 'create-service', data=default_event_data +# ) + +# assert response.status_code == 201 +# assert response.data['id'] +# mock_send_new_event.assert_called_with( +# event_id=response.data['id'] +# ) + + diff --git a/backend_challenge/core/views.py b/backend_challenge/core/views.py index 31bbab0..d9bc8c9 100644 --- a/backend_challenge/core/views.py +++ b/backend_challenge/core/views.py @@ -7,12 +7,12 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -# import africastalking -# username = "sandbox" # use 'sandbox' for development in the test environment -# api_key = "c24b10b049468747684e01f846e1a7420e106584c144d187b62d39fe667b6a78" -# africastalking.initialize(username, api_key) +from rest_framework.exceptions import NotFound +from .tasks import send_sms + + class Customer_Create(APIView): permission_classes = [IsAuthenticated] @@ -21,7 +21,7 @@ class Customer_Create(APIView): """ def post(self, request): serializer = CustomerSerializer(data=request.data) - if serializer.is_valid(): + if serializer.is_valid(raise_exception=True): serializer.save(user=request.user) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -29,13 +29,21 @@ def post(self, request): class OrderListCreateAPIView(ListCreateAPIView): serializer_class = OrderSerializer permission_classes = [IsAuthenticated] + queryset = Order.objects.all() def get_queryset(self): - return Order.objects.all() + try: + customer=self.request.user.customer + except Customer.DoesNotExist: + raise NotFound('Please Create Customer Profile') + return self.queryset.filter(customer=customer) + def perform_create(self, serializer): - serializer.save(customer=self.request.user.customer) + if serializer.is_valid(raise_exception=True): + order=serializer.save() + diff --git a/backend_challenge/settings.py b/backend_challenge/settings.py index 967862b..fdd3c71 100644 --- a/backend_challenge/settings.py +++ b/backend_challenge/settings.py @@ -84,10 +84,10 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME':'orders', - 'USER':'postgres', - 'PASSWORD':'lucy', - 'HOST': 'localhost', + 'NAME':config('DB_NAME'), + 'USER':config('DB_USER'), + 'PASSWORD':config('DB_PASSWORD'), + 'HOST':config('DB_HOST') , 'PORT':5432, } } @@ -161,18 +161,29 @@ LOGIN_REDIRECT_URL = '/api/v1/customer' STATIC_URL = '/static/' +# CSRF_COOKIE_SECURE = True +# CSRF_COOKIE_HTTPONLY = True +# SECURE_HSTS_SECONDS = 60 * 60 * 24 * 7 * 52 # one year +# SECURE_HSTS_INCLUDE_SUBDOMAINS = True +# SECURE_SSL_REDIRECT = True +# SECURE_BROWSER_XSS_FILTER = True +# SECURE_CONTENT_TYPE_NOSNIFF = True +# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +# SESSION_COOKIE_SECURE = True + OIDC_RP_SIGN_ALGO = "RS256" OIDC_OP_JWKS_ENDPOINT ="https://www.googleapis.com/oauth2/v3/certs" OIDC_OP_AUTHORIZATION_ENDPOINT ="https://accounts.google.com/o/oauth2/v2/auth" OIDC_OP_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" OIDC_OP_USER_ENDPOINT="https://openidconnect.googleapis.com/v1/userinfo" -OIDC_RP_CLIENT_ID ="1084835314828-s8npea9jce01udttaf8td5627p9k06fg.apps.googleusercontent.com" -OIDC_RP_CLIENT_SECRET= "4ZbM0GBZVisPA9YrBP3lFcr5" +OIDC_RP_CLIENT_ID =config('OIDC_RP_CLIENT_ID') +OIDC_RP_CLIENT_SECRET= config('OIDC_RP_CLIENT_SECRET') BROKER_URL = 'redis://localhost:6379' CELERY_RESULT_BACKEND = 'redis://localhost:6379' CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = 'Africa/Nairobi' +AFRICASTALKING_API_KEY = config('AFRICASTALKING_API_KEY') django_heroku.settings(locals()) diff --git a/coverage.svg b/coverage.svg index 0fa9649..59d64b3 100644 --- a/coverage.svg +++ b/coverage.svg @@ -9,13 +9,13 @@ - + coverage coverage - 98% - 98% + 93% + 93% diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..bbebf96 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +DJANGO_SETTINGS_MODULE = backend_challenge.settings +python_files = tests.py test_*.py *_tests.py + +addopts = -v --nomigrations --ignore=venv --cov=. --cov-report=html \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 40e3c12..6ca4deb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ africastalking==1.2.3 amqp==5.0.1 +apipkg==1.5 asgiref==3.2.10 attrs==20.2.0 +autopep8==1.5.5 billiard==3.6.3.0 celery==5.0.1 certifi==2020.6.20 @@ -25,8 +27,11 @@ django-rest-swagger==2.2.0 djangorestframework==3.11.1 drf-spectacular==0.10.0 drf-yasg==1.20.0 +execnet==1.8.0 +Faker==6.5.0 flake8==3.8.3 gunicorn==20.0.4 +hypothesis==6.4.0 idna==2.10 importlib-metadata==2.0.0 inflection==0.5.1 @@ -58,6 +63,9 @@ pyrsistent==0.17.3 pytest==6.1.0 pytest-cov==2.10.1 pytest-django==3.10.0 +pytest-forked==1.3.0 +pytest-xdist==2.2.1 +python-dateutil==2.8.1 python-decouple==3.3 python3-openid==3.2.0 pytz==2020.1 @@ -72,7 +80,9 @@ simplejson==3.17.2 six==1.15.0 social-auth-app-django==4.0.0 social-auth-core==3.3.3 +sortedcontainers==2.3.0 sqlparse==0.3.1 +text-unidecode==1.3 toml==0.10.1 uritemplate==3.0.1 urllib3==1.25.10