forked from openedx-unsupported/ecommerce
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds unit tests for ecommerce subscriptions flow (#56)
- Loading branch information
1 parent
82c9ae6
commit cd0765f
Showing
15 changed files
with
1,201 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.