Skip to content

Commit

Permalink
Adds unit tests for ecommerce subscriptions flow (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
rehan99000 authored Oct 29, 2020
1 parent 82c9ae6 commit cd0765f
Show file tree
Hide file tree
Showing 15 changed files with 1,201 additions and 21 deletions.
4 changes: 2 additions & 2 deletions ecommerce/subscriptions/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def get_is_course_payments_enabled(self, product):
class Meta:
model = Product
fields = [
'id', 'title', 'date_created', 'subscription_type', 'subscription_actual_price', 'subscription_price',
'title', 'subscription_type', 'subscription_actual_price', 'subscription_price',
'subscription_status', 'display_order', 'partner_sku', 'is_course_payments_enabled'
]

Expand Down Expand Up @@ -325,6 +325,6 @@ def _create_conditional_offer(self, subscription, partner):
class Meta:
model = Product
fields = (
'id', 'title', 'date_created', 'date_updated', 'subscription_type', 'subscription_actual_price', 'subscription_price', 'subscription_status',
'title', 'subscription_type', 'subscription_actual_price', 'subscription_price', 'subscription_status',
'number_of_courses', 'subscription_duration_value', 'subscription_duration_unit'
)
Empty file.
161 changes: 161 additions & 0 deletions ecommerce/subscriptions/api/v2/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""
Subscription related factories.
"""
from datetime import date
from factory import post_generation, RelatedFactory, SubFactory
from factory.django import DjangoModelFactory
from factory.fuzzy import (
FuzzyChoice,
FuzzyDate,
FuzzyFloat,
FuzzyInteger,
FuzzyText,
)
from oscar.core.loading import get_model
from oscar.test.factories import (
BenefitFactory,
ConditionFactory,
ConditionalOfferFactory,
ProductAttributeValueFactory,
ProductClassFactory,
)
from random import choice

from ecommerce.subscriptions.benefits import SubscriptionBenefit
from ecommerce.subscriptions.conditions import SubscriptionCondition
from ecommerce.subscriptions.custom import class_path

Basket = get_model('basket', 'Basket')
BasketAttribute = get_model('basket', 'BasketAttribute')
BasketAttributeType = get_model('basket', 'BasketAttributeType')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
Product = get_model('catalogue', 'Product')

SUBSCRIPTION_ATTRIBUTES_VALUES = lambda subscription: {
'limited-access': {
'number_of_courses': FuzzyInteger(1, 10).fuzz(),
'subscription_duration_value': FuzzyInteger(1, 10).fuzz(),
'subscription_duration_unit': choice(subscription.attr.get_attribute_by_code('subscription_duration_unit').option_group.options.all()),
'subscription_actual_price': FuzzyFloat(100.0, 500.0).fuzz(),
'subscription_price': FuzzyFloat(10.0, 99.0).fuzz(),
'subscription_display_order': FuzzyInteger(1, 5).fuzz(),
'subscription_status': FuzzyChoice((True, False)).fuzz()
},
'full-access-courses': {
'subscription_duration_value': FuzzyInteger(1, 10).fuzz(),
'subscription_duration_unit': choice(subscription.attr.get_attribute_by_code('subscription_duration_unit').option_group.options.all()),
'subscription_actual_price': FuzzyFloat(100.0, 500.0).fuzz(),
'subscription_price': FuzzyFloat(10.0, 99.0).fuzz(),
'subscription_display_order': FuzzyInteger(1, 5).fuzz(),
'subscription_status': FuzzyChoice((True, False)).fuzz()
},
'full-access-time-period': {
'number_of_courses': FuzzyInteger(1, 10).fuzz(),
'subscription_actual_price': FuzzyFloat(100.0, 500.0).fuzz(),
'subscription_price': FuzzyFloat(10.0, 99.0).fuzz(),
'subscription_display_order': FuzzyInteger(1, 5).fuzz(),
'subscription_status': FuzzyChoice((True, False)).fuzz()
},
'lifetime-access': {
'subscription_actual_price': FuzzyFloat(100.0, 500.0).fuzz(),
'subscription_price': FuzzyFloat(10.0, 99.0).fuzz(),
'subscription_display_order': FuzzyInteger(1, 5).fuzz(),
'subscription_status': FuzzyChoice((True, False)).fuzz()
}
}


class SubscriptionFactory(DjangoModelFactory):
"""
Factory class for Subscription product.
"""
class Meta(object):
model = Product

title = FuzzyText()
product_class = SubFactory(ProductClassFactory)
stockrecords = RelatedFactory('oscar.test.factories.StockRecordFactory', 'product')

@post_generation
def product_attributes(subscription, create, extracted, **kwargs):
"""
Post generation product to set required subscription product attributes.
"""
if not create:
return

subscription_type = kwargs.get('subscription_type')
if subscription_type and not extracted:
subscription_type_attribute = subscription.attr.get_attribute_by_code('subscription_type')
subscription_type_option = subscription_type_attribute.option_group.options.get(option=subscription_type)
setattr(subscription.attr, 'subscription_type', subscription_type_option)
for attribute_key, attribute_value in SUBSCRIPTION_ATTRIBUTES_VALUES(subscription)[subscription_type].items():
setattr(subscription.attr, attribute_key, attribute_value)

elif extracted:
subscription_type = extracted.get('subscription_type')
if subscription_type:
subscription_type_attribute = subscription.attr.get_attribute_by_code('subscription_type')
subscription_type_option = subscription_type_attribute.option_group.options.get(option=subscription_type)
setattr(subscription.attr, 'subscription_type', subscription_type_option)
for attribute_key in SUBSCRIPTION_ATTRIBUTES_VALUES(subscription)[subscription_type].keys():
if attribute_key == 'subscription_duration_unit':
duration_unit_attribute = subscription.attr.get_attribute_by_code('subscription_duration_unit')
duration_unit_option = duration_unit_attribute.option_group.options.get(option=extracted.get(attribute_key))
setattr(subscription.attr, attribute_key, duration_unit_option)
else:
setattr(subscription.attr, attribute_key, extracted.get(attribute_key))

else:
subscription_type = choice(subscription.attr.get_attribute_by_code('subscription_type').option_group.options.all())
setattr(subscription.attr, 'subscription_type', subscription_type)
for attribute_key, attribute_value in SUBSCRIPTION_ATTRIBUTES_VALUES(subscription)[subscription_type.option].items():
setattr(subscription.attr, attribute_key, attribute_value)

setattr(subscription.attr, 'subscription_status', kwargs.get('subscription_status', subscription.attr.subscription_status))
subscription.attr.save()


class SubscriptionConditionFactory(ConditionFactory):
"""
Factory class for "SubscriptionCondition".
"""
class Meta(object):
model = SubscriptionCondition

range = None
type = ''
value = None
proxy_class = class_path(SubscriptionCondition)


class SubscriptionBenefitFactory(BenefitFactory):
"""
Factory class for "SubscriptionBenefit".
"""
range = None
type = ''
value = 100
proxy_class = class_path(SubscriptionBenefit)


class SubscriptionOfferFactory(ConditionalOfferFactory):
"""
Factory class for Subscription Conditional offer.
"""
benefit = SubFactory(SubscriptionBenefitFactory)
condition = SubFactory(SubscriptionConditionFactory)
offer_type = ConditionalOffer.SITE
status = ConditionalOffer.OPEN


class BasketAttributeFactory(DjangoModelFactory):
"""
Factory class for "BasketAttribute".
"""
class Meta(object):
model = BasketAttribute

basket = SubFactory(Basket)
attribute_type = SubFactory(BasketAttributeType)
value_text = FuzzyText()
161 changes: 161 additions & 0 deletions ecommerce/subscriptions/api/v2/tests/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""
Subscription product mixins.
"""
from oscar.test.factories import (
AttributeOptionFactory,
AttributeOptionGroupFactory,
CategoryFactory,
ProductAttributeFactory,
ProductAttributeValueFactory,
ProductCategoryFactory,
ProductClassFactory,
ProductFactory,
)
from oscar.core.loading import get_model

from ecommerce.core.constants import SUBSCRIPTION_CATEGORY_NAME, SUBSCRIPTION_PRODUCT_CLASS_NAME
from ecommerce.subscriptions.api.v2.tests.constants import SUBSCRIPTION_DURATION_UNIT_OPTIONS, SUBSCRIPTION_TYPES
from ecommerce.subscriptions.api.v2.tests.factories import SubscriptionFactory

Category = get_model('catalogue', 'Category')
ProductAttribute = get_model('catalogue', 'ProductAttribute')


class SubscriptionProductMixin(object):
"""
Mixin to provide a method to create a subscription for tests.
This mixin provides a method that creates a subscription product and sets up
all required attributes and relevant class instances.
"""
SUBSRIPTION_ATTRIBUTES = [
{
'name': 'Number of Courses',
'code': 'number_of_courses',
'type': ProductAttribute.INTEGER,
},
{
'name': 'Subscription Actual Price',
'code': 'subscription_actual_price',
'type': ProductAttribute.FLOAT,
},
{
'name': 'Subscription Display Order',
'code': 'subscription_display_order',
'type': ProductAttribute.INTEGER,
},
{
'name': 'Subscription Duration unit',
'code': 'subscription_duration_unit',
'type': ProductAttribute.OPTION,
},
{
'name': 'Subscription Duration value',
'code': 'subscription_duration_value',
'type': ProductAttribute.INTEGER,
},
{
'name': 'Subscription Price',
'code': 'subscription_price',
'type': ProductAttribute.FLOAT,
},
{
'name': 'Subscription Status',
'code': 'subscription_status',
'type': ProductAttribute.BOOLEAN,
},
{
'name': 'Subscription Type',
'code': 'subscription_type',
'type': ProductAttribute.OPTION,
},
]
SUBSCRIPTION_DURATION_UNIT_GROUP_NAME = 'Subscription Duration Units'
SUBSCRIPTION_TYPE_GROUP_NAME = 'Subscription Access Types'

def _create_subscription_type_attribute_option_group(self):
"""
Private method to create subscription type attribute group.
"""
subscription_type_option_group = AttributeOptionGroupFactory(name=self.SUBSCRIPTION_TYPE_GROUP_NAME)
for option in SUBSCRIPTION_TYPES:
AttributeOptionFactory(option=option, group=subscription_type_option_group)

return subscription_type_option_group

def _create_subscription_duration_unit_attribute_option_group(self):
"""
Private method to create subscription duration unit attribute group.
"""
duration_unit_option_group = AttributeOptionGroupFactory(name=self.SUBSCRIPTION_DURATION_UNIT_GROUP_NAME)
for option in SUBSCRIPTION_DURATION_UNIT_OPTIONS:
AttributeOptionFactory(option=option, group=duration_unit_option_group)

return duration_unit_option_group

def _create_subscription_product_class(self):
"""
Private method to set up subscription product class.
Creates the subscription class, sets all of subscription attributes and attribute option groups.
"""
subscription_product_class = ProductClassFactory(
name=SUBSCRIPTION_PRODUCT_CLASS_NAME,
requires_shipping=False,
track_stock=False
)
for attribute in self.SUBSRIPTION_ATTRIBUTES:
product_attribute = ProductAttributeFactory(
name=attribute['name'],
code=attribute['code'],
type=attribute['type'],
product_class=subscription_product_class,
)

if product_attribute.code == 'subscription_type':
product_attribute.option_group = self._create_subscription_type_attribute_option_group()

if product_attribute.code == 'subscription_duration_unit':
product_attribute.option_group = self._create_subscription_duration_unit_attribute_option_group()

product_attribute.save()

return subscription_product_class

def create_subscription(self, attributes=None, subscription_type=None, subscription_status=True, **kwargs):
"""
Public method to create a dummy subscription.
This method creates a subscription with the given parameters (type, status etc.) as well as the
subscription category and subscription product category factory.
Arguments:
attributes (dict): Dict of subscription attributes to create a custom subscription
subscription_type (str): Subscription type string to create a specific type of subscription
subscription_status (bool): Subscription type to set for the subscription
**kwargs: Additional kwargs e.g. stockrecord partner to set for the subscription
Returns:
Product: A dummy subscription product with all relevant product attributes set
"""
Category.objects.all().delete()
if subscription_type not in SUBSCRIPTION_TYPES:
subscription_type = None

subscription_product_class = self._create_subscription_product_class()
subscription_category = CategoryFactory(
name=SUBSCRIPTION_CATEGORY_NAME,
)
subscription = SubscriptionFactory(
product_class=subscription_product_class,
product_attributes=attributes,
product_attributes__subscription_type=subscription_type,
product_attributes__subscription_status=subscription_status,
**kwargs
)
ProductCategoryFactory(
product=subscription,
category=subscription_category
)

return subscription
Loading

0 comments on commit cd0765f

Please sign in to comment.