Change "invalid_coupon" error into a result

This commit is contained in:
Ramon Wenger 2023-05-24 00:26:29 +02:00
parent 5e78d08c48
commit d1664592ce
5 changed files with 131 additions and 69 deletions

View File

@ -7,3 +7,8 @@ class FailureNode(graphene.Interface):
class SuccessNode(graphene.Interface): class SuccessNode(graphene.Interface):
message = graphene.String() message = graphene.String()
class Success(graphene.ObjectType):
class Meta:
interfaces = (SuccessNode,)

View File

@ -1,7 +1,7 @@
import graphene import graphene
from graphene import relay from graphene import relay
from api.types import FailureNode, SuccessNode from api.types import FailureNode, Success
from api.utils import get_object from api.utils import get_object
from books.models import Module, ContentBlock, Chapter from books.models import Module, ContentBlock, Chapter
from books.models.snapshot import Snapshot from books.models.snapshot import Snapshot
@ -14,23 +14,24 @@ class NotOwner(graphene.ObjectType):
interfaces = (FailureNode,) interfaces = (FailureNode,)
class Success(graphene.ObjectType):
class Meta:
interfaces = (SuccessNode,)
NotOwnerFailure = NotOwner(reason="Not the owner") NotOwnerFailure = NotOwner(reason="Not the owner")
DeleteSnapshotSuccess = Success(message='Snapshot deleted successfully') DeleteSnapshotSuccess = Success(message="Snapshot deleted successfully")
class UpdateSnapshotResult(graphene.Union): class UpdateSnapshotResult(graphene.Union):
class Meta: class Meta:
types = (SnapshotNode, NotOwner,) types = (
SnapshotNode,
NotOwner,
)
class DeleteSnapshotResult(graphene.Union): class DeleteSnapshotResult(graphene.Union):
class Meta: class Meta:
types = (Success, NotOwner,) types = (
Success,
NotOwner,
)
class CreateSnapshot(relay.ClientIDMutation): class CreateSnapshot(relay.ClientIDMutation):
@ -43,12 +44,12 @@ class CreateSnapshot(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
module_slug = args.get('module') module_slug = args.get("module")
module = Module.objects.get(slug=module_slug) module = Module.objects.get(slug=module_slug)
user = info.context.user user = info.context.user
# todo: check user # todo: check user
# raise NotImplementedError('Permissions') # raise NotImplementedError('Permissions')
selected_class_id = args.get('selected_class') selected_class_id = args.get("selected_class")
selected_class = get_object(SchoolClass, selected_class_id) selected_class = get_object(SchoolClass, selected_class_id)
snapshot = Snapshot.objects.create_snapshot(module, selected_class, user) snapshot = Snapshot.objects.create_snapshot(module, selected_class, user)
return cls(snapshot=snapshot, success=True) return cls(snapshot=snapshot, success=True)
@ -63,8 +64,8 @@ class UpdateSnapshot(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
id = args.get('id') id = args.get("id")
title = args.get('title') title = args.get("title")
user = info.context.user user = info.context.user
snapshot = get_object(Snapshot, id) snapshot = get_object(Snapshot, id)
@ -84,7 +85,7 @@ class DeleteSnapshot(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
id = args.get('id') id = args.get("id")
user = info.context.user user = info.context.user
snapshot = get_object(Snapshot, id) snapshot = get_object(Snapshot, id)
@ -105,14 +106,17 @@ class ApplySnapshot(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
snapshot_id = args.get('snapshot') snapshot_id = args.get("snapshot")
snapshot = get_object(Snapshot, snapshot_id) snapshot = get_object(Snapshot, snapshot_id)
user = info.context.user user = info.context.user
selected_class_id = args.get('selected_class') selected_class_id = args.get("selected_class")
selected_class = get_object(SchoolClass, selected_class_id) selected_class = get_object(SchoolClass, selected_class_id)
# permission check # permission check
if not selected_class.users.filter(username=user.username).exists() or not user.is_teacher(): if (
raise PermissionError('Not allowed') not selected_class.users.filter(username=user.username).exists()
or not user.is_teacher()
):
raise PermissionError("Not allowed")
# reset everything # reset everything
snapshot.reset(user, selected_class) snapshot.reset(user, selected_class)
@ -131,12 +135,12 @@ class ShareSnapshot(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
snapshot_id = args.get('snapshot') snapshot_id = args.get("snapshot")
shared = args.get('shared') shared = args.get("shared")
user = info.context.user user = info.context.user
snapshot = get_object(Snapshot, snapshot_id) snapshot = get_object(Snapshot, snapshot_id)
if snapshot.creator != user: if snapshot.creator != user:
raise PermissionError('Not permitted') raise PermissionError("Not permitted")
snapshot.shared = shared snapshot.shared = shared
snapshot.save() snapshot.save()
return cls(success=True, snapshot=snapshot) return cls(success=True, snapshot=snapshot)

View File

@ -5,6 +5,7 @@ from graphene import relay
from oauth.hep_client import HepClient, HepClientException from oauth.hep_client import HepClient, HepClientException
from oauth.models import OAuth2Token from oauth.models import OAuth2Token
from oauth.types import InvalidCouponFailure, RedeemCouponResult, RedeemCouponSuccess
from oauth.user_signup_login_handler import create_role_for_user from oauth.user_signup_login_handler import create_role_for_user
from users.models import License from users.models import License
@ -13,39 +14,46 @@ class Coupon(relay.ClientIDMutation):
class Input: class Input:
coupon_code = graphene.String() coupon_code = graphene.String()
success = graphene.Boolean() result = RedeemCouponResult()
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
coupon_code = kwargs.get('coupon_code').strip() coupon_code = kwargs.get("coupon_code").strip()
hep_client = HepClient() hep_client = HepClient()
try: try:
hep_id = info.context.user.hep_id hep_id = info.context.user.hep_id
except AttributeError: except AttributeError:
raise Exception('not_authenticated') raise Exception("not_authenticated")
try: try:
# todo: differenciate between invalid_coupon and other errors like "delivery_address_not_set" # todo: differenciate between invalid_coupon and other errors like "delivery_address_not_set"
response = hep_client.redeem_coupon(coupon_code, hep_id, request=info.context) response = hep_client.redeem_coupon(
coupon_code, hep_id, request=info.context
)
except HepClientException: except HepClientException:
raise Exception('unknown_error') raise Exception("unknown_error")
if not response: if not response:
raise Exception('invalid_coupon') return cls(result=InvalidCouponFailure)
product = hep_client.entry_to_product(response['data'], now(), 'coupon') product = hep_client.entry_to_product(response["data"], now(), "coupon")
if not product: if not product:
raise Exception('non_myskillbox_product') raise Exception("non_myskillbox_product")
license = License.objects.create_license_for_role(info.context.user, product['activated'], product['raw'], license = License.objects.create_license_for_role(
product['license']['edition'], info.context.user,
product['order_id'], product['isbn']) product["activated"],
product["raw"],
product["license"]["edition"],
product["order_id"],
product["isbn"],
)
create_role_for_user(info.context.user, license.for_role.key) create_role_for_user(info.context.user, license.for_role.key)
return cls(success=True) return cls(result=RedeemCouponSuccess)
class CouponMutations: class CouponMutations:
@ -77,4 +85,3 @@ class Logout(graphene.Mutation):
class OauthMutations(object): class OauthMutations(object):
logout = Logout.Field() logout = Logout.Field()
coupon = Coupon.Field() coupon = Coupon.Field()

View File

@ -26,7 +26,7 @@ REDEEM_MYSKILLBOX_SUCCESS_RESPONSE = {
"price": 100, "price": 100,
"price_total": 100, "price_total": 100,
"amount": 1, "amount": 1,
"authors": [] "authors": [],
} }
} }
@ -45,29 +45,27 @@ REDEEM_OTHER_LICENSE_RESPONSE = {
"price": 100, "price": 100,
"price_total": 100, "price_total": 100,
"amount": 1, "amount": 1,
"authors": [] "authors": [],
} }
} }
INVALID_LICENSE = { INVALID_LICENSE = {
"message": "The given data was invalid.", "message": "The given data was invalid.",
"errors": { "errors": {"code": ["The coupons was already redeemed."]},
"code": [
"The coupons was already redeemed."
]
}
} }
class CouponTests(TestCase): class CouponTests(TestCase):
def setUp(self): def setUp(self):
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3) self.user = UserFactory(
username="aschi@iterativ.ch", email="aschi@iterativ.ch", hep_id=3
)
Role.objects.create_default_roles() Role.objects.create_default_roles()
self.teacher_role = Role.objects.get_default_teacher_role() self.teacher_role = Role.objects.get_default_teacher_role()
Oauth2TokenFactory(user=self.user) Oauth2TokenFactory(user=self.user)
# adding session # adding session
request = RequestFactory().post('/') request = RequestFactory().post("/")
middleware = SessionMiddleware() middleware = SessionMiddleware()
middleware.process_request(request) middleware.process_request(request)
request.user = self.user request.user = self.user
@ -75,23 +73,33 @@ class CouponTests(TestCase):
self.client = Client(schema=schema, context_value=request) self.client = Client(schema=schema, context_value=request)
def make_coupon_mutation(self, coupon_code, client): def make_coupon_mutation(self, coupon_code, client):
mutation = ''' mutation = """
mutation Coupon($input: CouponInput!){ mutation Coupon($input: CouponInput!){
coupon(input: $input) { coupon(input: $input) {
success result {
__typename
...on InvalidCoupon {
reason
}
...on Success {
message
} }
} }
''' }
}
"""
return client.execute(mutation, variables={ return client.execute(
'input': { mutation, variables={"input": {"couponCode": coupon_code}}
'couponCode': coupon_code )
}
})
@patch.object(BaseApp, 'post', return_value=MockResponse(200, data=REDEEM_MYSKILLBOX_SUCCESS_RESPONSE)) @patch.object(
BaseApp,
"post",
return_value=MockResponse(200, data=REDEEM_MYSKILLBOX_SUCCESS_RESPONSE),
)
def test_user_has_valid_skillbox_coupon(self, response_mock): def test_user_has_valid_skillbox_coupon(self, response_mock):
result = self.make_coupon_mutation('COUPON--1234', self.client) result = self.make_coupon_mutation("COUPON--1234", self.client)
user_role = self.user.user_roles.get(user=self.user) user_role = self.user.user_roles.get(user=self.user)
self.assertEqual(user_role.role.key, Role.objects.TEACHER_KEY) self.assertEqual(user_role.role.key, Role.objects.TEACHER_KEY)
@ -101,48 +109,66 @@ class CouponTests(TestCase):
school_class = SchoolClass.objects.get(users__in=[self.user]) school_class = SchoolClass.objects.get(users__in=[self.user])
self.assertIsNotNone(school_class) self.assertIsNotNone(school_class)
self.assertTrue(result.get('data').get('coupon').get('success')) self.assertTrue(result.get("data").get("coupon").get("success"))
self.assertTrue(self.user.is_authenticated) self.assertTrue(self.user.is_authenticated)
@patch.object(BaseApp, 'post', return_value=MockResponse(200, data=REDEEM_OTHER_LICENSE_RESPONSE)) @patch.object(
BaseApp,
"post",
return_value=MockResponse(200, data=REDEEM_OTHER_LICENSE_RESPONSE),
)
def test_user_has_valid_non_skillbox_coupon(self, response_mock): def test_user_has_valid_non_skillbox_coupon(self, response_mock):
result = self.make_coupon_mutation('COUPON--1234', self.client) result = self.make_coupon_mutation("COUPON--1234", self.client)
try: try:
self.user.user_roles.get(user=self.user) self.user.user_roles.get(user=self.user)
self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: Should not have created user role") self.fail(
"CouponTests.test_user_has_valid_non_skillbox_coupon: Should not have created user role"
)
except: except:
pass pass
try: try:
License.objects.get(licensee=self.user) License.objects.get(licensee=self.user)
self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: License should not exist") self.fail(
"CouponTests.test_user_has_valid_non_skillbox_coupon: License should not exist"
)
except License.DoesNotExist: except License.DoesNotExist:
pass pass
self.assertEqual(result.get('errors')[0].get('message'), 'non_myskillbox_product') self.assertEqual(
result.get("errors")[0].get("message"), "non_myskillbox_product"
)
self.assertTrue(self.user.is_authenticated) self.assertTrue(self.user.is_authenticated)
@patch.object(BaseApp, 'post', return_value=MockResponse(404, data=INVALID_LICENSE)) @patch.object(BaseApp, "post", return_value=MockResponse(404, data=INVALID_LICENSE))
def test_user_has_invalid_coupon(self, response_mock): def test_user_has_invalid_coupon(self, response_mock):
result = self.make_coupon_mutation('COUPON--1234', self.client) result = self.make_coupon_mutation("COUPON--1234", self.client)
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon') self.assertEqual(
result.get("data").get("coupon").get("result").get("__typename"),
"InvalidCoupon",
)
@patch.object(BaseApp, 'post', return_value=MockResponse(422, data=INVALID_LICENSE)) @patch.object(BaseApp, "post", return_value=MockResponse(422, data=INVALID_LICENSE))
def test_user_has_already_used_coupon(self, response_mock): def test_user_has_already_used_coupon(self, response_mock):
result = self.make_coupon_mutation('COUPON--1234', self.client) result = self.make_coupon_mutation("COUPON--1234", self.client)
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon') self.assertEqual(
result.get("data").get("coupon").get("result").get("__typename"),
"InvalidCoupon",
)
@patch.object(BaseApp, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon'])) @patch.object(
BaseApp, "put", return_value=MockResponse(200, data=["201", "Invalid Coupon"])
)
def test_unauthenticated_user_cannot_redeem(self, response_mock): def test_unauthenticated_user_cannot_redeem(self, response_mock):
request = RequestFactory().post('/') request = RequestFactory().post("/")
middleware = SessionMiddleware() middleware = SessionMiddleware()
middleware.process_request(request) middleware.process_request(request)
request.session.save() request.session.save()
client = Client(schema=schema, context_value=request) client = Client(schema=schema, context_value=request)
result = self.make_coupon_mutation('COUPON--1234', client) result = self.make_coupon_mutation("COUPON--1234", client)
self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated') self.assertEqual(result.get("errors")[0].get("message"), "not_authenticated")

20
server/oauth/types.py Normal file
View File

@ -0,0 +1,20 @@
import graphene
from api.types import FailureNode, Success
class InvalidCoupon(graphene.ObjectType):
class Meta:
interfaces = (FailureNode,)
RedeemCouponSuccess = Success(message="Coupon successfully redeemed.")
InvalidCouponFailure = InvalidCoupon(reason="Invalid coupon provided.")
class RedeemCouponResult(graphene.Union):
class Meta:
types = (
Success,
InvalidCoupon,
)