diff --git a/server/api/schema.py b/server/api/schema.py index 7421800f..6b12e99d 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -10,7 +10,7 @@ from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery from basicknowledge.queries import BasicKnowledgeQuery from books.schema.mutations import BookMutations from books.schema.queries import BookQuery -from core.schema.mutations.coupon import CouponMutations +from oauth.mutations import CouponMutations from core.schema.mutations.main import CoreMutations from notes.mutations import NoteMutations from objectives.mutations import ObjectiveMutations diff --git a/server/core/schema/mutations/main.py b/server/core/schema/mutations/main.py index ea0140c7..1587a29f 100644 --- a/server/core/schema/mutations/main.py +++ b/server/core/schema/mutations/main.py @@ -1,13 +1,4 @@ -# -*- coding: utf-8 -*- -# -# ITerativ GmbH -# http://www.iterativ.ch/ -# -# Copyright (c) 2018 ITerativ GmbH. All rights reserved. -# -# Created on 22.10.18 -# @author: chrigu -from core.schema.mutations.coupon import Coupon +from oauth.mutations import Coupon from core.schema.mutations.logout import Logout diff --git a/server/core/tests/test_coupon.py b/server/core/tests/test_coupon.py deleted file mode 100644 index 18f733d7..00000000 --- a/server/core/tests/test_coupon.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# -# ITerativ GmbH -# http://www.iterativ.ch/ -# -# Copyright (c) 2020 ITerativ GmbH. All rights reserved. -# -# Created on 03.02.20 -# @author: chrigu -from unittest.mock import patch - -import requests -from django.contrib.sessions.middleware import SessionMiddleware -from django.test import TestCase, RequestFactory -from graphene.test import Client - -from api.schema import schema -from core.factories import UserFactory -from oauth.hep_client import HepClient -from users.tests.mock_hep_data_factory import MockResponse, VALID_TEACHERS_ORDERS -from users.models import License, Role, SchoolClass, UserRole - - -class CouponTests(TestCase): - def setUp(self): - Role.objects.create_default_roles() - - self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3) - Role.objects.create_default_roles() - self.teacher_role = Role.objects.get_default_teacher_role() - - # adding session - request = RequestFactory().post('/') - middleware = SessionMiddleware() - middleware.process_request(request) - request.user = self.user - request.session.save() - self.client = Client(schema=schema, context_value=request) - - def make_coupon_mutation(self, coupon_code, client): - mutation = ''' - mutation Coupon($input: CouponInput!){ - coupon(input: $input) { - success - } - } - ''' - - return client.execute(mutation, variables={ - 'input': { - 'couponCode': coupon_code - } - }) - - @patch.object(requests, 'put', return_value=MockResponse(200, data=['200', 'Coupon successfully redeemed'])) - @patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS) - def test_user_has_valid_coupon(self, orders_mock, response_mock): - result = self.make_coupon_mutation('COUPON--1234', self.client) - - user_role_key = self.user.user_roles.get(user=self.user).role.key - self.assertEqual(user_role_key, Role.objects.TEACHER_KEY) - license = License.objects.get(licensee=self.user) - self.assertIsNotNone(license) - - school_class = SchoolClass.objects.get(users__in=[self.user]) - self.assertIsNotNone(school_class) - - self.assertTrue(result.get('data').get('coupon').get('success')) - self.assertTrue(self.user.is_authenticated) - - @patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon'])) - def test_user_has_invalid_coupon(self, response_mock): - result = self.make_coupon_mutation('COUPON--1234', self.client) - - self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon') - - @patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon'])) - def test_unauthenticated_user_cannot_redeem(self, response_mock): - - request = RequestFactory().post('/') - middleware = SessionMiddleware() - middleware.process_request(request) - request.session.save() - client = Client(schema=schema, context_value=request) - - result = self.make_coupon_mutation('COUPON--1234', client) - - self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated') diff --git a/server/oauth/hep_client.py b/server/oauth/hep_client.py index 60d523d1..e436f6fa 100644 --- a/server/oauth/hep_client.py +++ b/server/oauth/hep_client.py @@ -137,10 +137,10 @@ class HepClient: return None response_data = response.json() - if response_data[0] == '200': - return None - return response_data[0] + # todo handle 404, 402 + + return response_data def _extract_myskillbox_products(self, eorders): products = [] @@ -152,19 +152,25 @@ class HepClient: status = eorder['status'] for entry in eorder['entries']: - if is_myskillbox_product(entry['isbn']): - product = { + product = self.entry_to_product(entry, self._get_item_activation(eorder), status) + + if product: + products.append(product) + + return products + + def entry_to_product(self, entry, activation_date, status): + if is_myskillbox_product(entry['isbn']): + return { 'raw': entry, - 'activated': self._get_item_activation(eorder), + 'activated': activation_date, 'status': status, 'order_id': entry['id'], 'license': MYSKILLBOX_LICENSES[entry['isbn']], 'isbn': entry['isbn'] } - products.append(product) - - return products + return None def _get_item_activation(self, eorder): if 'created_at' in eorder: diff --git a/server/core/schema/mutations/coupon.py b/server/oauth/mutations.py similarity index 56% rename from server/core/schema/mutations/coupon.py rename to server/oauth/mutations.py index 9d0f672d..b695e073 100644 --- a/server/core/schema/mutations/coupon.py +++ b/server/oauth/mutations.py @@ -1,8 +1,10 @@ import graphene +from django.utils.timezone import now from graphene import relay from oauth.hep_client import HepClient, HepClientException -from oauth.user_signup_login_handler import check_and_create_licenses, create_role_for_user +from oauth.user_signup_login_handler import create_role_for_user +from users.models import License class Coupon(relay.ClientIDMutation): @@ -22,18 +24,21 @@ class Coupon(relay.ClientIDMutation): raise Exception('not_authenticated') try: - response = hep_client.coupon_redeem(coupon_code, hep_id) + response = hep_client.redeem_coupon(coupon_code, hep_id, request=info) except HepClientException: raise Exception('unknown_error') if not response: raise Exception('invalid_coupon') - license, error_msg = check_and_create_licenses(hep_client, info.context.user) + product = hep_client.entry_to_product(response['data'], now(), 'coupon') - # todo fail if no license - if error_msg: - raise Exception(error_msg) + if not product: + raise Exception('non_myskillbox_product') + + license = License.objects.create_license_for_role(info.context.user, product['activated'], product['raw'], + product['license']['edition'], + product['order_id'], product['isbn']) create_role_for_user(info.context.user, license.for_role.key) diff --git a/server/oauth/tests/test_coupon.py b/server/oauth/tests/test_coupon.py new file mode 100644 index 00000000..e54a55dd --- /dev/null +++ b/server/oauth/tests/test_coupon.py @@ -0,0 +1,149 @@ +from unittest.mock import patch + +import requests +from django.contrib.auth.models import AnonymousUser +from django.contrib.sessions.middleware import SessionMiddleware +from django.test import TestCase, RequestFactory +from graphene.test import Client + +from api.schema import schema +from core.factories import UserFactory +from oauth.factories import Oauth2TokenFactory +from users.tests.mock_hep_data_factory import MockResponse, VALID_TEACHERS_ORDERS +from users.models import License, Role, SchoolClass + +REDEEM_MYSKILLBOX_SUCCESS_RESPONSE = { + "data": { + "id": 3433, + "uri": "\/products\/myskillbox-lehrpersonen", + "url": None, + "title": "mySkillbox für Lehrpersonen ", + "subtitle": "Lizenz gültig für 1 Jahr", + "isbn": "978-3-0355-1861-0", + "slug": "myskillbox-lehrpersonen", + "product_type": "eLehrmittel", + "product_form": "", + "cover": "https:\/\/hep-verlag.fra1.digitaloceanspaces.com\/staging\/products\/2921\/978-3-0355-1861-0.jpg", + "price": 100, + "price_total": 100, + "amount": 1, + "authors": [] + } +} + +REDEEM_OTHER_LICENSE_RESPONSE = { + "data": { + "id": 3433, + "uri": "\/products\/someothe", + "url": None, + "title": "Ein e-Lehrmittel", + "subtitle": "Lizenz gültig für 1 Jahr", + "isbn": "111-2-3333-4444-0", + "slug": "some-other", + "product_type": "eLehrmittel", + "product_form": "", + "cover": "https:\/\/hep-verlag.fra1.digitaloceanspaces.com\/staging\/products\/2921\/978-3-0355-123.jpg", + "price": 100, + "price_total": 100, + "amount": 1, + "authors": [] + } +} + +INVALID_LICENSE = { + "message": "The given data was invalid.", + "errors": { + "code": [ + "The coupons was already redeemed." + ] + } +} + + +class CouponTests(TestCase): + def setUp(self): + self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3) + Role.objects.create_default_roles() + self.teacher_role = Role.objects.get_default_teacher_role() + Oauth2TokenFactory(user=self.user) + + # adding session + request = RequestFactory().post('/') + middleware = SessionMiddleware() + middleware.process_request(request) + request.user = self.user + request.session.save() + self.client = Client(schema=schema, context_value=request) + + def make_coupon_mutation(self, coupon_code, client): + mutation = ''' + mutation Coupon($input: CouponInput!){ + coupon(input: $input) { + success + } + } + ''' + + return client.execute(mutation, variables={ + 'input': { + 'couponCode': coupon_code + } + }) + + @patch.object(requests, 'post', return_value=MockResponse(200, data=REDEEM_MYSKILLBOX_SUCCESS_RESPONSE)) + def test_user_has_valid_skillbox_coupon(self, response_mock): + result = self.make_coupon_mutation('COUPON--1234', self.client) + + user_role_key = self.user.user_roles.get(user=self.user).role.key + self.assertEqual(user_role_key, Role.objects.TEACHER_KEY) + license = License.objects.get(licensee=self.user) + self.assertIsNotNone(license) + + school_class = SchoolClass.objects.get(users__in=[self.user]) + self.assertIsNotNone(school_class) + + self.assertTrue(result.get('data').get('coupon').get('success')) + self.assertTrue(self.user.is_authenticated) + + @patch.object(requests, 'post', return_value=MockResponse(200, data=REDEEM_OTHER_LICENSE_RESPONSE)) + def test_user_has_valid_non_skillbox_coupon(self, response_mock): + result = self.make_coupon_mutation('COUPON--1234', self.client) + + try: + self.user.user_roles.get(user=self.user).role.key + self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: Should not have created user role") + except: + pass + + try: + License.objects.get(licensee=self.user) + self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: License should not exist") + except License.DoesNotExist: + pass + + self.assertEqual(result.get('errors')[0].get('message'), 'non_myskillbox_product') + self.assertTrue(self.user.is_authenticated) + + @patch.object(requests, 'post', return_value=MockResponse(404, data=INVALID_LICENSE)) + def test_user_has_invalid_coupon(self, response_mock): + result = self.make_coupon_mutation('COUPON--1234', self.client) + + self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon') + + @patch.object(requests, 'post', return_value=MockResponse(422, data=INVALID_LICENSE)) + def test_user_has_already_used_coupon(self, response_mock): + result = self.make_coupon_mutation('COUPON--1234', self.client) + + self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon') + + @patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon'])) + def test_unauthenticated_user_cannot_redeem(self, response_mock): + request = RequestFactory().post('/') + middleware = SessionMiddleware() + middleware.process_request(request) + request.session.save() + client = Client(schema=schema, context_value=request) + + result = self.make_coupon_mutation('COUPON--1234', client) + + self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated') diff --git a/server/oauth/user_signup_login_handler.py b/server/oauth/user_signup_login_handler.py index a426be51..9b01f237 100644 --- a/server/oauth/user_signup_login_handler.py +++ b/server/oauth/user_signup_login_handler.py @@ -46,14 +46,12 @@ def check_and_create_licenses(hep_client, user, token): license = License.objects.create_license_for_role(user, product['activated'], product['raw'], product['license']['edition'], product['order_id'], product['isbn']) - # todo handle no license case else: return None, NO_VALID_LICENSE return license, None - def create_role_for_user(user, role_key): UserRole.objects.get_or_create_role_for_user(user, role_key)