Fix client, refactor user handling, fix tests

This commit is contained in:
Christian Cueni 2020-01-30 14:46:13 +01:00
parent 45f887287f
commit 773547c883
7 changed files with 152 additions and 149 deletions

View File

@ -102,6 +102,12 @@ class HepClient:
response = self._call('/rest/V1/customers/me', additional_headers={'authorization': 'Bearer {}'.format(token)}) response = self._call('/rest/V1/customers/me', additional_headers={'authorization': 'Bearer {}'.format(token)})
return response.json() return response.json()
def customer_activate(self, confirmation_key):
response = self._call('/rest/V1/customers/me/activate', method='post', data={
'confirmationKey': confirmation_key
})
return response.json()
def _customer_orders(self, admin_token, customer_id): def _customer_orders(self, admin_token, customer_id):
url = ("/rest/V1/orders/?searchCriteria[filterGroups][0][filters][0][" url = ("/rest/V1/orders/?searchCriteria[filterGroups][0][filters][0]["
"field]=customer_id&searchCriteria[filterGroups][0][filters][0][value]={}".format(customer_id)) "field]=customer_id&searchCriteria[filterGroups][0][filters][0][value]={}".format(customer_id))
@ -126,11 +132,18 @@ class HepClient:
products = [] products = []
for order_item in orders['items']: for order_item in orders['items']:
status = ''
if 'status' in order_item:
status = order_item['status']
for item in order_item['items']: for item in order_item['items']:
if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN or item['sku'] == MYSKILLBOX_STUDENT_EDITION_ISBN: if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN or item['sku'] == MYSKILLBOX_STUDENT_EDITION_ISBN:
product = { product = {
'raw': item, 'raw': item,
'activated': self._get_item_activation(order_item) 'activated': self._get_item_activation(order_item),
'status': status
} }
if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN: if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN:
@ -144,14 +157,16 @@ class HepClient:
return products return products
def _get_item_activation(self, item): def _get_item_activation(self, item):
for history in item['status_histories']: if 'created_at' in item:
# todo can there be no date? return datetime.strptime(item['created_at'], '%Y-%m-%d %H:%M:%S')
if history['comment'] == 'payed by couponcode':
return datetime.strptime(history['created_at'], '%Y-%m-%d %H:%M:%S')
def _get_relevant_product(self, products): def _get_relevant_product(self, products):
def filter_inactive_products(product): def filter_valid_products(product):
if product['status'] != 'complete':
return False
if product['edition'] == 'teacher': if product['edition'] == 'teacher':
expiry_delta = product['activated'] + timedelta(TEACHER_EDITION_DURATION) expiry_delta = product['activated'] + timedelta(TEACHER_EDITION_DURATION)
else: else:
@ -162,7 +177,7 @@ class HepClient:
else: else:
return False return False
active_products = list(filter(filter_inactive_products, products)) active_products = list(filter(filter_valid_products, products))
print(active_products) print(active_products)
# todo can a teacher have multiple licenses? # todo can a teacher have multiple licenses?

View File

@ -24,17 +24,33 @@ class HepClientTestCases(TestCase):
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': {}, 'raw': {},
'activated': self.now - timedelta(2*TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(2*TEACHER_EDITION_DURATION),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': {}, 'raw': {},
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': {}, 'raw': {},
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
'status': 'complete'
}
]
relevant_product = self.hep_client._get_relevant_product(products)
self.assertIsNone(relevant_product)
def test_has_no_not_completed_product(self):
products = [
{
'edition': 'teacher',
'raw': {},
'activated': self.now - timedelta(7),
'status': 'not'
} }
] ]
@ -48,21 +64,24 @@ class HepClientTestCases(TestCase):
'raw': { 'raw': {
'id': 0 'id': 0
}, },
'activated': self.now - timedelta(7) 'activated': self.now - timedelta(7),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': { 'raw': {
'id': 1 'id': 1
}, },
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': { 'raw': {
'id': 2 'id': 2
}, },
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
'status': 'complete'
} }
] ]
@ -76,21 +95,24 @@ class HepClientTestCases(TestCase):
'raw': { 'raw': {
'id': 0 'id': 0
}, },
'activated': self.now - timedelta(7) 'activated': self.now - timedelta(7),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': { 'raw': {
'id': 1 'id': 1
}, },
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
'status': 'complete'
}, },
{ {
'edition': 'teacher', 'edition': 'teacher',
'raw': { 'raw': {
'id': 2 'id': 2
}, },
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION) 'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
'status': 'complete'
} }
] ]

View File

@ -11,10 +11,9 @@ import graphene
from django.conf import settings from django.conf import settings
from graphene import relay from graphene import relay
from core.hep_client import HepClient, HepClientException from core.hep_client import HepClient, HepClientException, HepClientUnauthorizedException
from core.views import SetPasswordView from core.views import SetPasswordView
from registration.models import License from registration.models import License
from registration.serializers import RegistrationSerializer
from users.models import User, Role, UserRole, SchoolClass from users.models import User, Role, UserRole, SchoolClass
@ -39,51 +38,29 @@ class Registration(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
first_name = kwargs.get('firstname_input') confirmation_key = kwargs.get('confirmationKey')
last_name = kwargs.get('lastname_input')
email = kwargs.get('email_input')
license_key = kwargs.get('license_key_input')
registration_data = {
'first_name': first_name,
'last_name': last_name,
'email': email,
'license_key': license_key,
}
serializer = RegistrationSerializer(data=registration_data) hep_client = HepClient()
if serializer.is_valid(): try:
user_data = hep_client.customer_activate(confirmation_key)
except HepClientUnauthorizedException:
return cls.return_login_error('invalid_credentials')
except HepClientException:
return cls.return_login_error('unknown_error')
if settings.USE_LOCAL_REGISTRATION: try:
return cls.create_local_user(serializer, info) user = User.objects.get(hep_id=user_data['id'])
else: except User.DoesNotExist:
hep_client = HepClient() user = User.objects.create_user_from_hep(user_data)
try: # show verfiy page
email_available = hep_client.is_email_available(serializer['email'])
except HepClientException:
# Todo: handle error from exception (set on object, code & message)
return cls(success=False, errors=None)
if not email_available: # errors = []
errors = [MutationError(field='email', errors=['already_exists'])] # for key, value in serializer.errors.items():
return cls(success=False, errors=errors) # error = MutationError(field=key, errors=[])
# for field_error in serializer.errors[key]:
try: # error.errors.append(PublicFieldError(code=field_error.code))
response = hep_client.customer_create(serializer.data, None)
except HepClientException:
# Todo: handle error from exception (set on object, code & message)
return cls(success=False, errors=None)
# create or update local user
# show verfiy page
errors = []
for key, value in serializer.errors.items():
error = MutationError(field=key, errors=[])
for field_error in serializer.errors[key]:
error.errors.append(PublicFieldError(code=field_error.code))
errors.append(error) errors.append(error)

View File

@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-10-08
# @author: chrigu <christian.cueni@iterativ.ch>
from django.conf import settings
from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.fields import CharField, EmailField
from django.utils.translation import ugettext_lazy as _
from registration.models import License
class RegistrationSerializer(serializers.Serializer):
first_name = CharField(allow_blank=False)
last_name = CharField(allow_blank=False)
email = EmailField(allow_blank=False)
license_key = CharField(allow_blank=False)
skillbox_license = None
def validate_email(self, value):
lower_email = value.lower()
if not settings.USE_LOCAL_REGISTRATION:
return lower_email
# the email is used as username
if len(get_user_model().objects.filter(username=lower_email)) > 0:
raise serializers.ValidationError(_(u'Diese E-Mail ist bereits registriert'))
elif len(get_user_model().objects.filter(email=lower_email)) > 0:
raise serializers.ValidationError(_(u'Dieser E-Mail ist bereits registriert'))
else:
return lower_email
def validate_license_key(self, value):
return value

View File

@ -14,8 +14,7 @@ from django.contrib.auth import authenticate, login
from graphene import relay from graphene import relay
from core.hep_client import HepClient, HepClientUnauthorizedException, HepClientException from core.hep_client import HepClient, HepClientUnauthorizedException, HepClientException
from registration.models import License from users.user_signup_login_handler import handle_user_and_verify_products
from users.models import MagentoToken, User, Role, UserRole, SchoolClass
class LoginError(graphene.ObjectType): class LoginError(graphene.ObjectType):
@ -43,7 +42,6 @@ class Login(relay.ClientIDMutation):
else: else:
hep_client = HepClient() hep_client = HepClient()
token = kwargs.get('token') token = kwargs.get('token')
try: try:
@ -53,45 +51,10 @@ class Login(relay.ClientIDMutation):
except HepClientException: except HepClientException:
return cls.return_login_error('unknown_error') return cls.return_login_error('unknown_error')
try: user, error_msg = handle_user_and_verify_products(user_data, token)
user = User.objects.get(email=username)
except User.DoesNotExist:
user = User.objects.create_user_from_hep(user_data)
#todo is this needed? if error_msg:
magento_token, created = MagentoToken.objects.get_or_create(user=user) return cls.return_login_error(error_msg)
magento_token.token = token
magento_token.save()
try:
if not hep_client.is_email_verified(user_data):
return cls.return_login_error('email_not_verified')
except HepClientException:
return cls.return_login_error('unknown_error')
try:
license = License.objects.get(licensee=user)
# Todo how handle invalid license? Cron Job? How to select correct license? Save all licenses? History?
except License.DoesNotExist:
try:
product = hep_client.myskillbox_product_for_customer(settings.HEP_ADMIN_TOKEN, user.hep_id)
except HepClientException:
return cls.return_login_error('unknown_error')
if product:
license = License.objects.create_license_for_role(user, product['activated'],
product['raw'], product['edition'])
# todo handle no license case
else:
return cls.return_login_error('no_valid_license')
UserRole.objects.create_role_for_user(user, license.for_role.key)
if license.for_role.key == Role.objects.TEACHER_KEY:
SchoolClass.create_default_group_for_teacher(user)
if not license.is_valid():
return cls.return_login_error('no_valid_license')
login(info.context, user) login(info.context, user)
return cls(success=True, errors=[]) return cls(success=True, errors=[])

View File

@ -31,10 +31,9 @@ TOKEN = 'abcd12345!'
def make_orders_valid(order_items): def make_orders_valid(order_items):
for order_item in order_items['items']: for order_item in order_items['items']:
for status in order_item['status_histories']: if 'created_at' in order_item:
if status['comment'] == 'payed by couponcode': yesterday = datetime.now() - timedelta(1)
yesterday = datetime.now() - timedelta(1) order_item['created_at'] = datetime.strftime(yesterday, '%Y-%m-%d %H:%M:%S')
status['created_at'] = datetime.strftime(yesterday, '%Y-%m-%d %H:%M:%S')
return order_items return order_items
@ -109,6 +108,9 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_can_login_with_local_user_and_valid_local_license(self, me_mock): def test_user_can_login_with_local_user_and_valid_local_license(self, me_mock):
self.user.hep_id = ME_DATA['id']
self.user.save()
now = timezone.now() now = timezone.now()
expiry_date = now + timedelta(365) expiry_date = now + timedelta(365)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
@ -120,7 +122,7 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS) @patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_teacher_can_login_with_local_user_and_remote_license(self, order_mock, me_token): def test_teacher_can_login_with_remote_user_and_remote_license(self, order_mock, me_token):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(ME_DATA['email'], TOKEN)
user = User.objects.get(email=ME_DATA['email']) user = User.objects.get(email=ME_DATA['email'])
@ -139,9 +141,9 @@ class PasswordResetTests(TestCase):
@patch.object(HepClient, '_customer_orders', return_value=VALID_STUDENT_ORDERS) @patch.object(HepClient, '_customer_orders', return_value=VALID_STUDENT_ORDERS)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_student_can_login_with_local_user_and_remote_license(self, order_mock, me_token): def test_student_can_login_with_remote_user_and_remote_license(self, order_mock, me_token):
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
result = self.make_login_mutation(ME_DATA['email'], TOKEN)
user = User.objects.get(email=ME_DATA['email']) user = User.objects.get(email=ME_DATA['email'])
user_role_key = user.user_roles.get(user=user).role.key user_role_key = user.user_roles.get(user=user).role.key
@ -165,7 +167,7 @@ class PasswordResetTests(TestCase):
def test_user_with_unconfirmed_email_cannot_login(self, me_mock, post_mock): def test_user_with_unconfirmed_email_cannot_login(self, me_mock, post_mock):
result = self.make_login_mutation(ME_DATA['email'], TOKEN) result = self.make_login_mutation(ME_DATA['email'], TOKEN)
user = User.objects.get(email=ME_DATA['email']) User.objects.get(email=ME_DATA['email'])
self.assertFalse(result.get('data').get('login').get('success')) self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified') self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'email_not_verified')
@ -178,8 +180,9 @@ class PasswordResetTests(TestCase):
self.assertFalse(result.get('data').get('login').get('success')) self.assertFalse(result.get('data').get('login').get('success'))
self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'no_valid_license') self.assertEqual(result.get('data').get('login').get('errors')[0].get('field'), 'no_valid_license')
@patch.object(HepClient, 'myskillbox_product_for_customer', return_value=None)
@patch.object(HepClient, 'customer_me', return_value=ME_DATA) @patch.object(HepClient, 'customer_me', return_value=ME_DATA)
def test_user_cannot_login_local_license_invalid(self, me_mock): def test_user_cannot_login_local_license_invalid(self, product_mock, me_mock):
now = timezone.now() now = timezone.now()
expiry_date = now - timedelta(1) expiry_date = now - timedelta(1)
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save() LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
#
# Created on 30.01.20
# @author: chrigu <christian.cueni@iterativ.ch>
from django.conf import settings
from core.hep_client import HepClient, HepClientException
from registration.models import License
from users.models import User, MagentoToken, UserRole, Role, SchoolClass
def handle_user_and_verify_products(user_data, token):
hep_client = HepClient()
try:
user = User.objects.get(hep_id=user_data['id'])
except User.DoesNotExist:
user = User.objects.create_user_from_hep(user_data)
# todo check if email has changed
# todo is this needed?
magento_token, created = MagentoToken.objects.get_or_create(user=user)
magento_token.token = token
magento_token.save()
try:
if not hep_client.is_email_verified(user_data):
return user, 'email_not_verified'
except HepClientException:
return user, 'unknown_error'
try:
license = License.objects.get(licensee=user)
# Todo how handle invalid license? Cron Job? How to select correct license? Save all licenses? History?
except License.DoesNotExist:
try:
# todo is admin token valid, save it? do we need it?
product = hep_client.myskillbox_product_for_customer(settings.HEP_ADMIN_TOKEN, user.hep_id)
except HepClientException:
return user, 'unknown_error'
if product:
license = License.objects.create_license_for_role(user, product['activated'],
product['raw'], product['edition'])
# todo handle no license case
else:
return user, 'no_valid_license'
UserRole.objects.create_role_for_user(user, license.for_role.key)
if license.for_role.key == Role.objects.TEACHER_KEY:
SchoolClass.create_default_group_for_teacher(user)
if not license.is_valid():
return user, 'no_valid_license'
return user, None