Changeset - 5debbb2ac82e
[Not reviewed]
Merge
0 10 0
Christopher Neugebauer - 8 years ago 2016-04-06 07:41:08
chrisjrn@gmail.com
Merge branch 'random_fixes'
10 files changed with 109 insertions and 65 deletions:
0 comments (0 inline, 0 general)
registrasion/controllers/invoice.py
Show inline comments
 
from decimal import Decimal
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.db import transaction
 
from django.db.models import Sum
 

	
 
from registrasion import models as rego
 

	
 
from cart import CartController
 

	
 

	
 
class InvoiceController(object):
 

	
 
    def __init__(self, invoice):
 
        self.invoice = invoice
 
        self.update_validity()  # Make sure this invoice is up-to-date
 

	
 
    @classmethod
 
    def for_cart(cls, cart):
 
        ''' Returns an invoice object for a given cart at its current revision.
 
        If such an invoice does not exist, the cart is validated, and if valid,
 
        an invoice is generated.'''
 

	
 
        try:
 
            invoice = rego.Invoice.objects.get(
 
                cart=cart,
 
                cart_revision=cart.revision,
 
                void=False,
 
            )
 
        except ObjectDoesNotExist:
 
            cart_controller = CartController(cart)
 
            cart_controller.validate_cart()  # Raises ValidationError on fail.
 

	
 
            # Void past invoices for this cart
 
            rego.Invoice.objects.filter(cart=cart).update(void=True)
 

	
 
            invoice = cls._generate(cart)
 

	
 
        return InvoiceController(invoice)
 

	
 
    @classmethod
 
    def resolve_discount_value(cls, item):
 
        try:
 
            condition = rego.DiscountForProduct.objects.get(
 
                discount=item.discount,
 
                product=item.product
 
            )
 
        except ObjectDoesNotExist:
 
            condition = rego.DiscountForCategory.objects.get(
 
                discount=item.discount,
 
                category=item.product.category
 
            )
 
        if condition.percentage is not None:
 
            value = item.product.price * (condition.percentage / 100)
 
        else:
 
            value = condition.price
 
        return value
 

	
 
    @classmethod
 
    @transaction.atomic
 
    def _generate(cls, cart):
 
        ''' Generates an invoice for the given cart. '''
 
        invoice = rego.Invoice.objects.create(
 
            user=cart.user,
 
            cart=cart,
 
            cart_revision=cart.revision,
 
            value=Decimal()
 
        )
 
        invoice.save()
 

	
 
        # TODO: calculate line items.
 
        product_items = rego.ProductItem.objects.filter(cart=cart)
 

	
 
        if len(product_items) == 0:
 
            raise ValidationError("Your cart is empty.")
 

	
 
        product_items = product_items.order_by(
 
            "product__category__order", "product__order"
 
        )
 
        discount_items = rego.DiscountItem.objects.filter(cart=cart)
 
        invoice_value = Decimal()
 
        for item in product_items:
 
            product = item.product
 
            line_item = rego.LineItem.objects.create(
 
                invoice=invoice,
 
                description="%s - %s" % (product.category.name, product.name),
 
                quantity=item.quantity,
 
                price=product.price,
 
            )
 
            line_item.save()
 
            invoice_value += line_item.quantity * line_item.price
 

	
 
        for item in discount_items:
 
            line_item = rego.LineItem.objects.create(
 
                invoice=invoice,
 
                description=item.discount.description,
 
                quantity=item.quantity,
 
                price=cls.resolve_discount_value(item) * -1,
 
            )
 
            line_item.save()
 
            invoice_value += line_item.quantity * line_item.price
 

	
 
        # TODO: calculate line items from discounts
 
        invoice.value = invoice_value
 

	
 
        if invoice.value == 0:
 
            invoice.paid = True
 

	
 
        invoice.save()
 

	
 
        return invoice
 

	
 
    def update_validity(self):
 
        ''' Updates the validity of this invoice if the cart it is attached to
 
        has updated. '''
 
        if self.invoice.cart is not None:
 
            if self.invoice.cart.revision != self.invoice.cart_revision:
 
                self.void()
 

	
 
    def void(self):
 
        ''' Voids the invoice if it is valid to do so. '''
 
        if self.invoice.paid:
 
            raise ValidationError("Paid invoices cannot be voided, "
 
                                  "only refunded.")
 
        self.invoice.void = True
 
        self.invoice.save()
 

	
 
    @transaction.atomic
 
    def pay(self, reference, amount):
 
        ''' Pays the invoice by the given amount. If the payment
 
        equals the total on the invoice, finalise the invoice.
 
        (NB should be transactional.)
 
        '''
 
        if self.invoice.cart:
 
            cart = CartController(self.invoice.cart)
 
            cart.validate_cart()  # Raises ValidationError if invalid
 

	
 
        if self.invoice.void:
 
            raise ValidationError("Void invoices cannot be paid")
 

	
 
        if self.invoice.paid:
 
            raise ValidationError("Paid invoices cannot be paid again")
 

	
 
        ''' Adds a payment '''
 
        payment = rego.Payment.objects.create(
 
            invoice=self.invoice,
 
            reference=reference,
 
            amount=amount,
 
        )
 
        payment.save()
 

	
 
        payments = rego.Payment.objects.filter(invoice=self.invoice)
 
        agg = payments.aggregate(Sum("amount"))
 
        total = agg["amount__sum"]
 

	
 
        if total == self.invoice.value:
 
            self.invoice.paid = True
 

	
 
            if self.invoice.cart:
 
                cart = self.invoice.cart
 
                cart.active = False
 
                cart.save()
 

	
 
            self.invoice.save()
 

	
 
    @transaction.atomic
 
    def refund(self, reference, amount):
 
        ''' Refunds the invoice by the given amount. The invoice is
 
        marked as unpaid, and the underlying cart is marked as released.
 
        '''
 

	
 
        if self.invoice.void:
 
            raise ValidationError("Void invoices cannot be refunded")
 

	
 
        ''' Adds a payment '''
 
        payment = rego.Payment.objects.create(
 
            invoice=self.invoice,
 
            reference=reference,
 
            amount=0 - amount,
 
        )
 
        payment.save()
 

	
 
        self.invoice.paid = False
 
        self.invoice.void = True
 

	
 
        if self.invoice.cart:
 
            cart = self.invoice.cart
 
            cart.released = True
 
            cart.save()
 

	
 
        self.invoice.save()
registrasion/forms.py
Show inline comments
 
import models as rego
 

	
 
from django import forms
 

	
 

	
 
# Products forms -- none of these have any fields: they are to be subclassed
 
# and the fields added as needs be.
 

	
 
class _ProductsForm(forms.Form):
 

	
 
    PRODUCT_PREFIX = "product_"
 

	
 
    ''' Base class for product entry forms. '''
 
    def __init__(self, *a, **k):
 
        if "product_quantities" in k:
 
            initial = self.initial_data(k["product_quantities"])
 
            k["initial"] = initial
 
            del k["product_quantities"]
 
        super(_ProductsForm, self).__init__(*a, **k)
 

	
 
    @classmethod
 
    def field_name(cls, product):
 
        return cls.PRODUCT_PREFIX + ("%d" % product.id)
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        ''' Sets the base_fields on this _ProductsForm to allow selecting
 
        from the provided products. '''
 
        pass
 

	
 
    @classmethod
 
    def initial_data(cls, product_quantites):
 
        ''' Prepares initial data for an instance of this form.
 
        product_quantities is a sequence of (product,quantity) tuples '''
 
        return {}
 

	
 
    def product_quantities(self):
 
        ''' Yields a sequence of (product, quantity) tuples from the
 
        cleaned form data. '''
 
        return iter([])
 

	
 

	
 
class _QuantityBoxProductsForm(_ProductsForm):
 
    ''' Products entry form that allows users to enter quantities
 
    of desired products. '''
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        for product in products:
 
            help_text = "$%d -- %s" % (product.price, product.description)
 
            if product.description:
 
                help_text = "$%d each -- %s" % (product.price, product.description)
 
            else:
 
                help_text = "$%d each" % product.price
 

	
 
            field = forms.IntegerField(
 
                label=product.name,
 
                help_text=help_text,
 
            )
 
            cls.base_fields[cls.field_name(product)] = field
 

	
 
    @classmethod
 
    def initial_data(cls, product_quantities):
 
        initial = {}
 
        for product, quantity in product_quantities:
 
            initial[cls.field_name(product)] = quantity
 

	
 
        return initial
 

	
 
    def product_quantities(self):
 
        for name, value in self.cleaned_data.items():
 
            if name.startswith(self.PRODUCT_PREFIX):
 
                product_id = int(name[len(self.PRODUCT_PREFIX):])
 
                yield (product_id, value, name)
 

	
 

	
 
class _RadioButtonProductsForm(_ProductsForm):
 
    ''' Products entry form that allows users to enter quantities
 
    of desired products. '''
 

	
 
    FIELD = "chosen_product"
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        choices = []
 
        for product in products:
 

	
 
            choice_text = "%s -- $%d" % (product.name, product.price)
 
            choices.append((product.id, choice_text))
 

	
 
        cls.base_fields[cls.FIELD] = forms.TypedChoiceField(
 
            label=category.name,
 
            widget=forms.RadioSelect,
 
            choices=choices,
 
            empty_value=0,
 
            coerce=int,
 
        )
 

	
 
    @classmethod
 
    def initial_data(cls, product_quantities):
 
        initial = {}
 

	
 
        for product, quantity in product_quantities:
 
            if quantity > 0:
 
                initial[cls.FIELD] = product.id
 
                break
 

	
 
        return initial
 

	
 
    def product_quantities(self):
 
        ours = self.cleaned_data[self.FIELD]
 
        choices = self.fields[self.FIELD].choices
 
        for choice_value, choice_display in choices:
 
            yield (
 
                choice_value,
 
                1 if ours == choice_value else 0,
 
                self.FIELD,
 
            )
 

	
 

	
 
def ProductsForm(category, products):
 
    ''' Produces an appropriate _ProductsForm subclass for the given render
 
    type. '''
 

	
 
    # Each Category.RENDER_TYPE value has a subclass here.
 
    RENDER_TYPES = {
 
        rego.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
 
        rego.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
 
    }
 

	
 
    # Produce a subclass of _ProductsForm which we can alter the base_fields on
 
    class ProductsForm(RENDER_TYPES[category.render_type]):
 
        pass
 

	
 
    ProductsForm.set_fields(category, products)
 
    return ProductsForm
 

	
 

	
 
class VoucherForm(forms.Form):
 
    voucher = forms.CharField(
 
        label="Voucher code",
 
        help_text="If you have a voucher code, enter it here",
 
        required=False,
 
    )
registrasion/templatetags/registrasion_tags.py
Show inline comments
 
from registrasion import models as rego
 
from registrasion.controllers.category import CategoryController
 

	
 
from collections import namedtuple
 
from django import template
 
from django.db.models import Sum
 

	
 
register = template.Library()
 

	
 
ProductAndQuantity = namedtuple("ProductAndQuantity", ["product", "quantity"])
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def available_categories(context):
 
    ''' Returns all of the available product categories '''
 
    return CategoryController.available_categories(context.request.user)
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def invoices(context):
 
    ''' Returns all of the invoices that this user has. '''
 
    return rego.Invoice.objects.filter(cart__user=context.request.user)
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def items_pending(context):
 
    ''' Returns all of the items that this user has in their current cart,
 
    and is awaiting payment. '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=True,
 
    )
 
    return all_items
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def items_purchased(context):
 
    ''' Returns all of the items that this user has purchased '''
 
def items_purchased(context, category=None):
 
    ''' Returns all of the items that this user has purchased, optionally
 
    from the given category. '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=False,
 
    )
 

	
 
    if category:
 
        all_items = all_items.filter(product__category=category)
 

	
 
    products = set(item.product for item in all_items)
 
    out = []
 
    for product in products:
 
        pp = all_items.filter(product=product)
 
        quantity = pp.aggregate(Sum("quantity"))["quantity__sum"]
 
        out.append(ProductAndQuantity(product, quantity))
 
    return out
 

	
 

	
 
@register.filter
 
def multiply(value, arg):
 
    ''' Multiplies value by arg '''
 
    return value * arg
registrasion/tests/cart_controller_helper.py
Show inline comments
 
from registrasion.controllers.cart import CartController
 
from registrasion import models as rego
 

	
 
from django.core.exceptions import ObjectDoesNotExist
 

	
 

	
 
class TestingCartController(CartController):
 

	
 
    def set_quantity(self, product, quantity, batched=False):
 
        ''' Sets the _quantity_ of the given _product_ in the cart to the given
 
        _quantity_. '''
 

	
 
        self.set_quantities(((product, quantity),))
 

	
 
    def add_to_cart(self, product, quantity):
 
        ''' Adds _quantity_ of the given _product_ to the cart. Raises
 
        ValidationError if constraints are violated.'''
 

	
 
        try:
 
            product_item = rego.ProductItem.objects.get(
 
                cart=self.cart,
 
                product=product)
 
            old_quantity = product_item.quantity
 
        except ObjectDoesNotExist:
 
            old_quantity = 0
 
        self.set_quantity(product, old_quantity + quantity)
 

	
 
    def next_cart(self):
 
        self.cart.active = False
 
        self.cart.save()
registrasion/tests/test_cart.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.test import TestCase
 

	
 
from registrasion import models as rego
 
from registrasion.controllers.product import ProductController
 

	
 
from cart_controller_helper import TestingCartController
 
from patch_datetime import SetTimeMixin
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class RegistrationCartTestCase(SetTimeMixin, TestCase):
 

	
 
    def setUp(self):
 
        super(RegistrationCartTestCase, self).setUp()
 

	
 
    @classmethod
 
    def setUpTestData(cls):
 
        cls.USER_1 = User.objects.create_user(
 
            username='testuser',
 
            email='test@example.com',
 
            password='top_secret')
 

	
 
        cls.USER_2 = User.objects.create_user(
 
            username='testuser2',
 
            email='test2@example.com',
 
            password='top_secret')
 

	
 
        cls.RESERVATION = datetime.timedelta(hours=1)
 

	
 
        cls.categories = []
 
        for i in xrange(2):
 
            cat = rego.Category.objects.create(
 
                name="Category " + str(i + 1),
 
                description="This is a test category",
 
                order=i,
 
                render_type=rego.Category.RENDER_TYPE_RADIO,
 
                required=False,
 
            )
 
            cat.save()
 
            cls.categories.append(cat)
 

	
 
        cls.CAT_1 = cls.categories[0]
 
        cls.CAT_2 = cls.categories[1]
 

	
 
        cls.products = []
 
        for i in xrange(4):
 
            prod = rego.Product.objects.create(
 
                name="Product " + str(i + 1),
 
                description="This is a test product.",
 
                category=cls.categories[i / 2],  # 2 products per category
 
                price=Decimal("10.00"),
 
                reservation_duration=cls.RESERVATION,
 
                limit_per_user=10,
 
                order=1,
 
            )
 
            prod.save()
 
            cls.products.append(prod)
 

	
 
        cls.PROD_1 = cls.products[0]
 
        cls.PROD_2 = cls.products[1]
 
        cls.PROD_3 = cls.products[2]
 
        cls.PROD_4 = cls.products[3]
 

	
 
        cls.PROD_4.price = Decimal("5.00")
 
        cls.PROD_4.save()
 

	
 
        # Burn through some carts -- this made some past EC tests fail
 
        current_cart = TestingCartController.for_user(cls.USER_1)
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(cls.USER_2)
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
    @classmethod
 
    def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
 
        limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create(
 
            description=name,
 
            mandatory=True,
 
            limit=limit,
 
            start_time=start_time,
 
            end_time=end_time
 
        )
 
        limit_ceiling.save()
 
        limit_ceiling.products.add(cls.PROD_1, cls.PROD_2)
 
        limit_ceiling.save()
 

	
 
    @classmethod
 
    def make_category_ceiling(
 
            cls, name, limit=None, start_time=None, end_time=None):
 
        limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create(
 
            description=name,
 
            mandatory=True,
 
            limit=limit,
 
            start_time=start_time,
 
            end_time=end_time
 
        )
 
        limit_ceiling.save()
 
        limit_ceiling.categories.add(cls.CAT_1)
 
        limit_ceiling.save()
 

	
 
    @classmethod
 
    def make_discount_ceiling(
 
            cls, name, limit=None, start_time=None, end_time=None,
 
            percentage=100):
 
        limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create(
 
            description=name,
 
            start_time=start_time,
 
            end_time=end_time,
 
            limit=limit,
 
        )
 
        limit_ceiling.save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=limit_ceiling,
 
            product=cls.PROD_1,
 
            percentage=percentage,
 
            quantity=10,
 
        ).save()
 

	
 
    @classmethod
 
    def new_voucher(self, code="VOUCHER", limit=1):
 
        voucher = rego.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code=code,
 
            limit=limit,
 
        )
 
        voucher.save()
 
        return voucher
 

	
 

	
 
class BasicCartTests(RegistrationCartTestCase):
 

	
 
    def test_get_cart(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        old_cart = current_cart
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        self.assertNotEqual(old_cart.cart, current_cart.cart)
 

	
 
        current_cart2 = TestingCartController.for_user(self.USER_1)
 
        self.assertEqual(current_cart.cart, current_cart2.cart)
 

	
 
    def test_add_to_cart_collapses_product_items(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Add a product twice
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Count of products for a given user should be collapsed.
 
        items = rego.ProductItem.objects.filter(
 
            cart=current_cart.cart,
 
            product=self.PROD_1)
 
        self.assertEqual(1, len(items))
 
        item = items[0]
 
        self.assertEquals(2, item.quantity)
 

	
 
    def test_set_quantity(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        def get_item():
 
            return rego.ProductItem.objects.get(
 
                cart=current_cart.cart,
 
                product=self.PROD_1)
 

	
 
        current_cart.set_quantity(self.PROD_1, 1)
 
        self.assertEqual(1, get_item().quantity)
 

	
 
        # Setting the quantity to zero should remove the entry from the cart.
 
        current_cart.set_quantity(self.PROD_1, 0)
 
        with self.assertRaises(ObjectDoesNotExist):
 
            get_item()
 

	
 
        current_cart.set_quantity(self.PROD_1, 9)
 
        self.assertEqual(9, get_item().quantity)
 

	
 
        with self.assertRaises(ValidationError):
 
            current_cart.set_quantity(self.PROD_1, 11)
 

	
 
        self.assertEqual(9, get_item().quantity)
 

	
 
        with self.assertRaises(ValidationError):
 
            current_cart.set_quantity(self.PROD_1, -1)
 

	
 
        self.assertEqual(9, get_item().quantity)
 

	
 
        current_cart.set_quantity(self.PROD_1, 2)
 
        self.assertEqual(2, get_item().quantity)
 

	
 
    def test_add_to_cart_product_per_user_limit(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # User should be able to add 1 of PROD_1 to the current cart.
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User should be able to add 1 of PROD_1 to the current cart.
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User should not be able to add 10 of PROD_1 to the current cart now,
 
        # because they have a limit of 10.
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 10)
 

	
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        # User should not be able to add 10 of PROD_1 to the current cart now,
 
        # even though it's a new cart.
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 10)
 

	
 
        # Second user should not be affected by first user's limits
 
        second_user_cart = TestingCartController.for_user(self.USER_2)
 
        second_user_cart.add_to_cart(self.PROD_1, 10)
 

	
 
    def set_limits(self):
 
        self.CAT_2.limit_per_user = 10
 
        self.PROD_2.limit_per_user = None
 
        self.PROD_3.limit_per_user = None
 
        self.PROD_4.limit_per_user = 6
 

	
 
        self.CAT_2.save()
 
        self.PROD_2.save()
 
        self.PROD_3.save()
 
        self.PROD_4.save()
 

	
 
    def test_per_user_product_limit_ignored_if_blank(self):
 
        self.set_limits()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        # There is no product limit on PROD_2, and there is no cat limit
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        # There is no product limit on PROD_3, but there is a cat limit
 
        current_cart.add_to_cart(self.PROD_3, 1)
 

	
 
    def test_per_user_category_limit_ignored_if_blank(self):
 
        self.set_limits()
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        # There is no product limit on PROD_2, and there is no cat limit
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        # There is no cat limit on PROD_1, but there is a prod limit
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_per_user_category_limit_only(self):
 
        self.set_limits()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Cannot add to cart if category limit is filled by one product.
 
        current_cart.set_quantity(self.PROD_3, 10)
 
        with self.assertRaises(ValidationError):
 
            current_cart.set_quantity(self.PROD_4, 1)
 

	
 
        # Can add to cart if category limit is not filled by one product
 
        current_cart.set_quantity(self.PROD_3, 5)
 
        current_cart.set_quantity(self.PROD_4, 5)
 
        # Cannot add to cart if category limit is filled by two products
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_3, 1)
 

	
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        # The category limit should extend across carts
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_3, 10)
 

	
 
    def test_per_user_category_and_product_limits(self):
 
        self.set_limits()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Hit both the product and category edges:
 
        current_cart.set_quantity(self.PROD_3, 4)
 
        current_cart.set_quantity(self.PROD_4, 6)
 
        with self.assertRaises(ValidationError):
 
            # There's unlimited PROD_3, but limited in the category
 
            current_cart.add_to_cart(self.PROD_3, 1)
 

	
 
        current_cart.set_quantity(self.PROD_3, 0)
 
        with self.assertRaises(ValidationError):
 
            # There's only 6 allowed of PROD_4
 
            current_cart.add_to_cart(self.PROD_4, 1)
 

	
 
        # The limits should extend across carts...
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.set_quantity(self.PROD_3, 4)
 

	
 
        with self.assertRaises(ValidationError):
 
            current_cart.set_quantity(self.PROD_3, 5)
 

	
 
        with self.assertRaises(ValidationError):
 
            current_cart.set_quantity(self.PROD_4, 1)
 

	
 
    def __available_products_test(self, item, quantity):
 
        self.set_limits()
 

	
 
        def get_prods():
 
            return ProductController.available_products(
 
                self.USER_1,
 
                products=[self.PROD_2, self.PROD_3, self.PROD_4],
 
            )
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        prods = get_prods()
 
        self.assertTrue(item in prods)
 
        current_cart.add_to_cart(item, quantity)
 
        self.assertTrue(item in prods)
 

	
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        prods = get_prods()
 
        self.assertTrue(item not in prods)
 

	
 
    def test_available_products_respects_category_limits(self):
 
        self.__available_products_test(self.PROD_3, 10)
 

	
 
    def test_available_products_respects_product_limits(self):
 
        self.__available_products_test(self.PROD_4, 6)
registrasion/tests/test_ceilings.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from django.core.exceptions import ValidationError
 

	
 
from cart_controller_helper import TestingCartController
 
from test_cart import RegistrationCartTestCase
 

	
 
from registrasion import models as rego
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class CeilingsTestCases(RegistrationCartTestCase):
 

	
 
    def test_add_to_cart_ceiling_limit(self):
 
        self.make_ceiling("Limit ceiling", limit=9)
 
        self.__add_to_cart_test()
 

	
 
    def test_add_to_cart_ceiling_category_limit(self):
 
        self.make_category_ceiling("Limit ceiling", limit=9)
 
        self.__add_to_cart_test()
 

	
 
    def __add_to_cart_test(self):
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # User should not be able to add 10 of PROD_1 to the current cart
 
        # because it is affected by limit_ceiling
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_2, 10)
 

	
 
        # User should be able to add 5 of PROD_1 to the current cart
 
        current_cart.add_to_cart(self.PROD_1, 5)
 

	
 
        # User should not be able to add 6 of PROD_2 to the current cart
 
        # because it is affected by CEIL_1
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_2, 6)
 

	
 
        # User should be able to add 5 of PROD_2 to the current cart
 
        current_cart.add_to_cart(self.PROD_2, 4)
 

	
 
    def test_add_to_cart_ceiling_date_range(self):
 
        self.make_ceiling(
 
            "date range ceiling",
 
            start_time=datetime.datetime(2015, 01, 01, tzinfo=UTC),
 
            end_time=datetime.datetime(2015, 02, 01, tzinfo=UTC))
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # User should not be able to add whilst we're before start_time
 
        self.set_time(datetime.datetime(2014, 01, 01, tzinfo=UTC))
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User should be able to add whilst we're during date range
 
        # On edge of start
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        # In middle
 
        self.set_time(datetime.datetime(2015, 01, 15, tzinfo=UTC))
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        # On edge of end
 
        self.set_time(datetime.datetime(2015, 02, 01, tzinfo=UTC))
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User should not be able to add whilst we're after date range
 
        self.set_time(datetime.datetime(2014, 01, 01, minute=01, tzinfo=UTC))
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_add_to_cart_ceiling_limit_reserved_carts(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 

	
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 

	
 
        first_cart = TestingCartController.for_user(self.USER_1)
 
        second_cart = TestingCartController.for_user(self.USER_2)
 

	
 
        first_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User 2 should not be able to add item to their cart
 
        # because user 1 has item reserved, exhausting the ceiling
 
        with self.assertRaises(ValidationError):
 
            second_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User 2 should be able to add item to their cart once the
 
        # reservation duration is elapsed
 
        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 
        second_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # User 2 pays for their cart
 
        second_cart.cart.active = False
 
        second_cart.cart.save()
 

	
 
        second_cart.next_cart()
 

	
 
        # User 1 should not be able to add item to their cart
 
        # because user 2 has paid for their reserved item, exhausting
 
        # the ceiling, regardless of the reservation time.
 
        self.add_timedelta(self.RESERVATION * 20)
 
        with self.assertRaises(ValidationError):
 
            first_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_validate_cart_fails_product_ceilings(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 
        self.__validation_test()
 

	
 
    def test_validate_cart_fails_product_discount_ceilings(self):
 
        self.make_discount_ceiling("Limit ceiling", limit=1)
 
        self.__validation_test()
 

	
 
    def __validation_test(self):
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 

	
 
        first_cart = TestingCartController.for_user(self.USER_1)
 
        second_cart = TestingCartController.for_user(self.USER_2)
 

	
 
        # Adding a valid product should validate.
 
        first_cart.add_to_cart(self.PROD_1, 1)
 
        first_cart.validate_cart()
 

	
 
        # Cart should become invalid if lapsed carts are claimed.
 
        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 

	
 
        # Unpaid cart within reservation window
 
        second_cart.add_to_cart(self.PROD_1, 1)
 
        with self.assertRaises(ValidationError):
 
            first_cart.validate_cart()
 

	
 
        # Paid cart outside the reservation window
 
        second_cart.cart.active = False
 
        second_cart.cart.save()
 

	
 
        second_cart.next_cart()
 
        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 
        with self.assertRaises(ValidationError):
 
            first_cart.validate_cart()
 

	
 
    def test_items_released_from_ceiling_by_refund(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 

	
 
        first_cart = TestingCartController.for_user(self.USER_1)
 
        first_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        first_cart.cart.active = False
 
        first_cart.cart.save()
 

	
 
        first_cart.next_cart()
 

	
 
        second_cart = TestingCartController.for_user(self.USER_2)
 
        with self.assertRaises(ValidationError):
 
            second_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        first_cart.cart.released = True
 
        first_cart.cart.save()
 
        first_cart.next_cart()
 

	
 
        second_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_discount_ceiling_only_counts_items_covered_by_ceiling(self):
 
        self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50)
 
        voucher = self.new_voucher(code="VOUCHER")
 

	
 
        discount = rego.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=100,
 
            quantity=1
 
        ).save()
 

	
 
        # Buy two of PROD_1, in separate carts:
 
        cart = TestingCartController.for_user(self.USER_1)
 
        # the 100% discount from the voucher should apply to the first item
 
        # and not the ceiling discount.
 
        cart.apply_voucher("VOUCHER")
 
        cart.add_to_cart(self.PROD_1, 1)
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 

	
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        # The second cart has no voucher attached, so should apply the
 
        # ceiling discount
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
registrasion/tests/test_discount.py
Show inline comments
...
 
@@ -75,341 +75,339 @@ class DiscountTestCase(RegistrationCartTestCase):
 
        ).save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=cls.PROD_4,
 
            percentage=amount,
 
            quantity=quantity,
 
        ).save()
 
        return discount
 

	
 
    def test_discount_is_applied(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
 
        # Discounts should be applied at this point...
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 

	
 
    def test_discount_is_applied_for_category(self):
 
        self.add_discount_prod_1_includes_cat_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_3, 1)
 

	
 
        # Discounts should be applied at this point...
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 

	
 
    def test_discount_does_not_apply_if_not_met(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
 
        # No discount should be applied as the condition is not met
 
        self.assertEqual(0, len(cart.cart.discountitem_set.all()))
 

	
 
    def test_discount_applied_out_of_order(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
 
        cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # No discount should be applied as the condition is not met
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 

	
 
    def test_discounts_collapse(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_2, 1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
 
        # Discounts should be applied and collapsed at this point...
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 

	
 
    def test_discounts_respect_quantity(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_2, 3)
 

	
 
        # There should be three items in the cart, but only two should
 
        # attract a discount.
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        self.assertEqual(2, discount_items[0].quantity)
 

	
 
    def test_multiple_discounts_apply_in_order(self):
 
        discount_full = self.add_discount_prod_1_includes_prod_2()
 
        discount_half = self.add_discount_prod_1_includes_prod_2(Decimal(50))
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.add_to_cart(self.PROD_2, 3)
 

	
 
        # There should be two discounts
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        discount_items.sort(key=lambda item: item.quantity)
 
        self.assertEqual(2, len(discount_items))
 
        # The half discount should be applied only once
 
        self.assertEqual(1, discount_items[0].quantity)
 
        self.assertEqual(discount_half.pk, discount_items[0].discount.pk)
 
        # The full discount should be applied twice
 
        self.assertEqual(2, discount_items[1].quantity)
 
        self.assertEqual(discount_full.pk, discount_items[1].discount.pk)
 

	
 
    def test_discount_applies_across_carts(self):
 
        self.add_discount_prod_1_includes_prod_2()
 

	
 
        # Enable the discount during the first cart.
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        # Use the discount in the second cart
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
 
        # The discount should be applied.
 
        self.assertEqual(1, len(cart.cart.discountitem_set.all()))
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        # The discount should respect the total quantity across all
 
        # of the user's carts.
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 2)
 

	
 
        # Having one item in the second cart leaves one more item where
 
        # the discount is applicable. The discount should apply, but only for
 
        # quantity=1
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        self.assertEqual(1, discount_items[0].quantity)
 

	
 
    def test_discount_applies_only_once_enabled(self):
 
        # Enable the discount during the first cart.
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 
        # This would exhaust discount if present
 
        cart.add_to_cart(self.PROD_2, 2)
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        self.add_discount_prod_1_includes_prod_2()
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 2)
 

	
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        self.assertEqual(2, discount_items[0].quantity)
 

	
 
    def test_category_discount_applies_once_per_category(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Add two items from category 2
 
        cart.add_to_cart(self.PROD_3, 1)
 
        cart.add_to_cart(self.PROD_4, 1)
 

	
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        # There is one discount, and it should apply to one item.
 
        self.assertEqual(1, len(discount_items))
 
        self.assertEqual(1, discount_items[0].quantity)
 

	
 
    def test_category_discount_applies_to_highest_value(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Add two items from category 2, add the less expensive one first
 
        cart.add_to_cart(self.PROD_4, 1)
 
        cart.add_to_cart(self.PROD_3, 1)
 

	
 
        discount_items = list(cart.cart.discountitem_set.all())
 
        # There is one discount, and it should apply to the more expensive.
 
        self.assertEqual(1, len(discount_items))
 
        self.assertEqual(self.PROD_3, discount_items[0].product)
 

	
 
    def test_discount_quantity_is_per_user(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        # Both users should be able to apply the same discount
 
        # in the same way
 
        for user in (self.USER_1, self.USER_2):
 
            cart = TestingCartController.for_user(user)
 
            cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
            cart.add_to_cart(self.PROD_3, 1)
 

	
 
            discount_items = list(cart.cart.discountitem_set.all())
 
            # The discount is applied.
 
            self.assertEqual(1, len(discount_items))
 

	
 
    # Tests for the discount.available_discounts enumerator
 
    def test_enumerate_no_discounts_for_no_input(self):
 
        discounts = discount.available_discounts(self.USER_1, [], [])
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_enumerate_no_discounts_if_condition_not_met(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_category_discount_appears_once_if_met_twice(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [self.CAT_2],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_with_category(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_with_product(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_category_discount_appears_once_with_two_valid_product(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3, self.PROD_4]
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_product_discount_appears_with_product(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
    def test_product_discount_does_not_appear_with_category(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=1)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 

	
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_1], [])
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_discount_quantity_is_correct_before_first_purchase(self):
 
        self.add_discount_prod_1_includes_cat_2(quantity=2)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity
 

	
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
 
        self.assertEqual(2, discounts[0].quantity)
 
        inv = InvoiceController.for_cart(cart.cart)
 
        inv.pay("Dummy reference", inv.invoice.value)
 
        self.assertTrue(inv.invoice.paid)
 

	
 
        cart.next_cart()
 

	
 
    def test_discount_quantity_is_correct_after_first_purchase(self):
 
        self.test_discount_quantity_is_correct_before_first_purchase()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity
 

	
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
 
        self.assertEqual(1, discounts[0].quantity)
 
        inv = InvoiceController.for_cart(cart.cart)
 
        inv.pay("Dummy reference", inv.invoice.value)
 
        self.assertTrue(inv.invoice.paid)
 

	
 
        cart.next_cart()
 

	
 
    def test_discount_is_gone_after_quantity_exhausted(self):
 
        self.test_discount_quantity_is_correct_after_first_purchase()
 
        discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
 
        self.assertEqual(0, len(discounts))
 

	
 
    def test_product_discount_enabled_twice_appears_twice(self):
 
        self.add_discount_prod_1_includes_prod_3_and_prod_4(quantity=2)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_3, self.PROD_4],
 
        )
 
        self.assertEqual(2, len(discounts))
 

	
 
    def test_discounts_are_released_by_refunds(self):
 
        self.add_discount_prod_1_includes_prod_2(quantity=2)
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(1, len(discounts))
 

	
 
        cart.cart.active = False  # Keep discount enabled
 
        cart.cart.save()
 
        cart.next_cart()
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 2)  # The discount will be exhausted
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(0, len(discounts))
 

	
 
        cart.cart.released = True
 
        cart.cart.save()
 
        cart.next_cart()
 

	
 
        discounts = discount.available_discounts(
 
            self.USER_1,
 
            [],
 
            [self.PROD_2],
 
        )
 
        self.assertEqual(1, len(discounts))
registrasion/tests/test_enabling_condition.py
Show inline comments
 
import pytz
 

	
 
from django.core.exceptions import ValidationError
 

	
 
from registrasion import models as rego
 
from registrasion.controllers.category import CategoryController
 
from cart_controller_helper import TestingCartController
 
from registrasion.controllers.product import ProductController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class EnablingConditionTestCases(RegistrationCartTestCase):
 

	
 
    @classmethod
 
    def add_product_enabling_condition(cls, mandatory=False):
 
        ''' Adds a product enabling condition: adding PROD_1 to a cart is
 
        predicated on adding PROD_2 beforehand. '''
 
        enabling_condition = rego.ProductEnablingCondition.objects.create(
 
            description="Product condition",
 
            mandatory=mandatory,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.products.add(cls.PROD_1)
 
        enabling_condition.enabling_products.add(cls.PROD_2)
 
        enabling_condition.save()
 

	
 
    @classmethod
 
    def add_product_enabling_condition_on_category(cls, mandatory=False):
 
        ''' Adds a product enabling condition that operates on a category:
 
        adding an item from CAT_1 is predicated on adding PROD_3 beforehand '''
 
        enabling_condition = rego.ProductEnablingCondition.objects.create(
 
            description="Product condition",
 
            mandatory=mandatory,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.categories.add(cls.CAT_1)
 
        enabling_condition.enabling_products.add(cls.PROD_3)
 
        enabling_condition.save()
 

	
 
    def add_category_enabling_condition(cls, mandatory=False):
 
        ''' Adds a category enabling condition: adding PROD_1 to a cart is
 
        predicated on adding an item from CAT_2 beforehand.'''
 
        enabling_condition = rego.CategoryEnablingCondition.objects.create(
 
            description="Category condition",
 
            mandatory=mandatory,
 
            enabling_category=cls.CAT_2,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.products.add(cls.PROD_1)
 
        enabling_condition.save()
 

	
 
    def test_product_enabling_condition_enables_product(self):
 
        self.add_product_enabling_condition()
 

	
 
        # Cannot buy PROD_1 without buying PROD_2
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_product_enabled_by_product_in_previous_cart(self):
 
        self.add_product_enabling_condition()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        # Create new cart and try to add PROD_1
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_product_enabling_condition_enables_category(self):
 
        self.add_product_enabling_condition_on_category()
 

	
 
        # Cannot buy PROD_1 without buying item from CAT_2
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        current_cart.add_to_cart(self.PROD_3, 1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_category_enabling_condition_enables_product(self):
 
        self.add_category_enabling_condition()
 

	
 
        # Cannot buy PROD_1 without buying PROD_2
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # PROD_3 is in CAT_2
 
        current_cart.add_to_cart(self.PROD_3, 1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_product_enabled_by_category_in_previous_cart(self):
 
        self.add_category_enabling_condition()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_3, 1)
 
        current_cart.cart.active = False
 
        current_cart.cart.save()
 

	
 
        current_cart.next_cart()
 

	
 
        # Create new cart and try to add PROD_1
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_multiple_non_mandatory_conditions(self):
 
        self.add_product_enabling_condition()
 
        self.add_category_enabling_condition()
 

	
 
        # User 1 is testing the product enabling condition
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        # Cannot add PROD_1 until a condition is met
 
        with self.assertRaises(ValidationError):
 
            cart_1.add_to_cart(self.PROD_1, 1)
 
        cart_1.add_to_cart(self.PROD_2, 1)
 
        cart_1.add_to_cart(self.PROD_1, 1)
 

	
 
        # User 2 is testing the category enabling condition
 
        cart_2 = TestingCartController.for_user(self.USER_2)
 
        # Cannot add PROD_1 until a condition is met
 
        with self.assertRaises(ValidationError):
 
            cart_2.add_to_cart(self.PROD_1, 1)
 
        cart_2.add_to_cart(self.PROD_3, 1)
 
        cart_2.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_multiple_mandatory_conditions(self):
 
        self.add_product_enabling_condition(mandatory=True)
 
        self.add_category_enabling_condition(mandatory=True)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        # Cannot add PROD_1 until both conditions are met
 
        with self.assertRaises(ValidationError):
 
            cart_1.add_to_cart(self.PROD_1, 1)
 
        cart_1.add_to_cart(self.PROD_2, 1)  # Meets the product condition
 
        with self.assertRaises(ValidationError):
 
            cart_1.add_to_cart(self.PROD_1, 1)
 
        cart_1.add_to_cart(self.PROD_3, 1)  # Meets the category condition
 
        cart_1.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_mandatory_conditions_are_mandatory(self):
 
        self.add_product_enabling_condition(mandatory=False)
 
        self.add_category_enabling_condition(mandatory=True)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        # Cannot add PROD_1 until both conditions are met
 
        with self.assertRaises(ValidationError):
 
            cart_1.add_to_cart(self.PROD_1, 1)
 
        cart_1.add_to_cart(self.PROD_2, 1)  # Meets the product condition
 
        with self.assertRaises(ValidationError):
 
            cart_1.add_to_cart(self.PROD_1, 1)
 
        cart_1.add_to_cart(self.PROD_3, 1)  # Meets the category condition
 
        cart_1.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_available_products_works_with_no_conditions_set(self):
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            category=self.CAT_1,
 
        )
 

	
 
        self.assertTrue(self.PROD_1 in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            category=self.CAT_2,
 
        )
 

	
 
        self.assertTrue(self.PROD_3 in prods)
 
        self.assertTrue(self.PROD_4 in prods)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            products=[self.PROD_1, self.PROD_2, self.PROD_3, self.PROD_4],
 
        )
 

	
 
        self.assertTrue(self.PROD_1 in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 
        self.assertTrue(self.PROD_3 in prods)
 
        self.assertTrue(self.PROD_4 in prods)
 

	
 
    def test_available_products_on_category_works_when_condition_not_met(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            category=self.CAT_1,
 
        )
 

	
 
        self.assertTrue(self.PROD_1 not in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_category_works_when_condition_is_met(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        cart_1.add_to_cart(self.PROD_2, 1)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            category=self.CAT_1,
 
        )
 

	
 
        self.assertTrue(self.PROD_1 in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_products_works_when_condition_not_met(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            products=[self.PROD_1, self.PROD_2],
 
        )
 

	
 
        self.assertTrue(self.PROD_1 not in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_available_products_on_products_works_when_condition_is_met(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        cart_1.add_to_cart(self.PROD_2, 1)
 

	
 
        prods = ProductController.available_products(
 
            self.USER_1,
 
            products=[self.PROD_1, self.PROD_2],
 
        )
 

	
 
        self.assertTrue(self.PROD_1 in prods)
 
        self.assertTrue(self.PROD_2 in prods)
 

	
 
    def test_category_enabling_condition_fails_if_cart_refunded(self):
 
        self.add_category_enabling_condition(mandatory=False)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_3, 1)
 

	
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        cart_2 = TestingCartController.for_user(self.USER_1)
 
        cart_2.add_to_cart(self.PROD_1, 1)
 
        cart_2.set_quantity(self.PROD_1, 0)
 

	
 
        cart.cart.released = True
 
        cart.cart.save()
 
        cart.next_cart()
 

	
 
        with self.assertRaises(ValidationError):
 
            cart_2.set_quantity(self.PROD_1, 1)
 

	
 
    def test_product_enabling_condition_fails_if_cart_refunded(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
 

	
 
        cart.cart.active = False
 
        cart.cart.save()
 

	
 
        cart.next_cart()
 

	
 
        cart_2 = TestingCartController.for_user(self.USER_1)
 
        cart_2.add_to_cart(self.PROD_1, 1)
 
        cart_2.set_quantity(self.PROD_1, 0)
 

	
 
        cart.cart.released = True
 
        cart.cart.save()
 
        cart.next_cart()
 

	
 
        with self.assertRaises(ValidationError):
 
            cart_2.set_quantity(self.PROD_1, 1)
 

	
 
    def test_available_categories(self):
 
        self.add_product_enabling_condition_on_category(mandatory=False)
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 

	
 
        cats = CategoryController.available_categories(
 
            self.USER_1,
 
        )
 

	
 
        self.assertFalse(self.CAT_1 in cats)
 
        self.assertTrue(self.CAT_2 in cats)
 

	
 
        cart_1.add_to_cart(self.PROD_3, 1)
 

	
 
        cats = CategoryController.available_categories(
 
            self.USER_1,
 
        )
 

	
 
        self.assertTrue(self.CAT_1 in cats)
 
        self.assertTrue(self.CAT_2 in cats)
 

	
 
    def test_validate_cart_when_enabling_conditions_become_unmet(self):
 
        self.add_product_enabling_condition(mandatory=False)
 

	
 
        cart = TestingCartController.for_user(self.USER_1)
 
        cart.add_to_cart(self.PROD_2, 1)
 
        cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Should pass
 
        cart.validate_cart()
 

	
 
        cart.set_quantity(self.PROD_2, 0)
 

	
 
        # Should fail
 
        with self.assertRaises(ValidationError):
 
            cart.validate_cart()
 

	
 
    def test_fix_simple_errors_resolves_unavailable_products(self):
 
        self.test_validate_cart_when_enabling_conditions_become_unmet()
 
        cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should just remove all of the unavailable products
 
        cart.fix_simple_errors()
 
        # Should now succeed
 
        cart.validate_cart()
 

	
 
        # Should keep PROD_2 in the cart
 
        items = rego.ProductItem.objects.filter(cart=cart.cart)
 
        self.assertFalse([i for i in items if i.product == self.PROD_1])
 

	
 
    def test_fix_simple_errors_does_not_remove_limited_items(self):
 
        cart = TestingCartController.for_user(self.USER_1)
 

	
 
        cart.add_to_cart(self.PROD_2, 1)
 
        cart.add_to_cart(self.PROD_1, 10)
 

	
 
        # Should just remove all of the unavailable products
 
        cart.fix_simple_errors()
 
        # Should now succeed
 
        cart.validate_cart()
 

	
 
        # Should keep PROD_2 in the cart
 
        # and also PROD_1, which is now exhausted for user.
 
        items = rego.ProductItem.objects.filter(cart=cart.cart)
 
        self.assertTrue([i for i in items if i.product == self.PROD_1])
registrasion/tests/test_invoice.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.core.exceptions import ValidationError
 

	
 
from registrasion import models as rego
 
from cart_controller_helper import TestingCartController
 
from registrasion.controllers.invoice import InvoiceController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class InvoiceTestCase(RegistrationCartTestCase):
 

	
 
    def test_create_invoice(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 
        # That invoice should have a single line item
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice)
 
        self.assertEqual(1, len(line_items))
 
        # That invoice should have a value equal to cost of PROD_1
 
        self.assertEqual(self.PROD_1.price, invoice_1.invoice.value)
 

	
 
        # Adding item to cart should produce a new invoice
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 

	
 
        # The old invoice should automatically be voided
 
        invoice_1_new = rego.Invoice.objects.get(pk=invoice_1.invoice.id)
 
        invoice_2_new = rego.Invoice.objects.get(pk=invoice_2.invoice.id)
 
        self.assertTrue(invoice_1_new.void)
 
        self.assertFalse(invoice_2_new.void)
 

	
 
        # Invoice should have two line items
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice)
 
        self.assertEqual(2, len(line_items))
 
        # Invoice should have a value equal to cost of PROD_1 and PROD_2
 
        self.assertEqual(
 
            self.PROD_1.price + self.PROD_2.price,
 
            invoice_2.invoice.value)
 

	
 
    def test_create_invoice_fails_if_cart_invalid(self):
 
        self.make_ceiling("Limit ceiling", limit=1)
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        self.add_timedelta(self.RESERVATION * 2)
 
        cart_2 = TestingCartController.for_user(self.USER_2)
 
        cart_2.add_to_cart(self.PROD_1, 1)
 

	
 
        # Now try to invoice the first user
 
        with self.assertRaises(ValidationError):
 
            InvoiceController.for_cart(current_cart.cart)
 

	
 
    def test_paying_invoice_makes_new_cart(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        invoice = InvoiceController.for_cart(current_cart.cart)
 
        invoice.pay("A payment!", invoice.invoice.value)
 

	
 
        # This payment is for the correct amount invoice should be paid.
 
        self.assertTrue(invoice.invoice.paid)
 

	
 
        # Cart should not be active
 
        self.assertFalse(invoice.invoice.cart.active)
 

	
 
        # Asking for a cart should generate a new one
 
        new_cart = TestingCartController.for_user(self.USER_1)
 
        self.assertNotEqual(current_cart.cart, new_cart.cart)
 

	
 
    def test_invoice_includes_discounts(self):
 
        voucher = rego.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code="VOUCHER",
 
            limit=1
 
        )
 
        voucher.save()
 
        discount = rego.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(50),
 
            quantity=1
 
        ).save()
 
        )
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        # That invoice should have two line items
 
        line_items = rego.LineItem.objects.filter(invoice=invoice_1.invoice)
 
        self.assertEqual(2, len(line_items))
 
        # That invoice should have a value equal to 50% of the cost of PROD_1
 
        self.assertEqual(
 
            self.PROD_1.price * Decimal("0.5"),
 
            invoice_1.invoice.value)
 

	
 
    def test_zero_value_invoice_is_automatically_paid(self):
 
        voucher = rego.Voucher.objects.create(
 
            recipient="Voucher recipient",
 
            code="VOUCHER",
 
            limit=1
 
        )
 
        discount = rego.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(100),
 
            quantity=1
 
        )
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        self.assertTrue(invoice_1.invoice.paid)
 

	
 
    def test_invoice_voids_self_if_cart_is_invalid(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        self.assertFalse(invoice_1.invoice.void)
 

	
 
        # Adding item to cart should produce a new invoice
 
        current_cart.add_to_cart(self.PROD_2, 1)
 
        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 

	
 
        # Viewing invoice_1's invoice should show it as void
 
        invoice_1_new = InvoiceController(invoice_1.invoice)
 
        self.assertTrue(invoice_1_new.invoice.void)
 

	
 
        # Viewing invoice_2's invoice should *not* show it as void
 
        invoice_2_new = InvoiceController(invoice_2.invoice)
 
        self.assertFalse(invoice_2_new.invoice.void)
 

	
 
    def test_voiding_invoice_creates_new_invoice(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        self.assertFalse(invoice_1.invoice.void)
 
        invoice_1.void()
 

	
 
        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 

	
 
    def test_cannot_pay_void_invoice(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        invoice_1.void()
 

	
 
        with self.assertRaises(ValidationError):
 
            invoice_1.pay("Reference", invoice_1.invoice.value)
 

	
 
    def test_cannot_void_paid_invoice(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        # Should be able to create an invoice after the product is added
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 

	
 
        invoice_1.pay("Reference", invoice_1.invoice.value)
 

	
 
        with self.assertRaises(ValidationError):
 
            invoice_1.void()
 

	
 
    def test_cannot_generate_blank_invoice(self):
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            invoice_1 = InvoiceController.for_cart(current_cart.cart)
registrasion/tests/test_voucher.py
Show inline comments
 
import datetime
 
import pytz
 

	
 
from decimal import Decimal
 
from django.core.exceptions import ValidationError
 
from django.db import IntegrityError
 

	
 
from registrasion import models as rego
 
from cart_controller_helper import TestingCartController
 
from registrasion.controllers.invoice import InvoiceController
 

	
 
from test_cart import RegistrationCartTestCase
 

	
 
UTC = pytz.timezone('UTC')
 

	
 

	
 
class VoucherTestCases(RegistrationCartTestCase):
 

	
 
    def test_apply_voucher(self):
 
        voucher = self.new_voucher()
 

	
 
        self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 

	
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        cart_1.apply_voucher(voucher.code)
 
        self.assertIn(voucher, cart_1.cart.vouchers.all())
 

	
 
        # Second user should not be able to apply this voucher (it's exhausted)
 
        cart_2 = TestingCartController.for_user(self.USER_2)
 
        with self.assertRaises(ValidationError):
 
            cart_2.apply_voucher(voucher.code)
 

	
 
        # After the reservation duration
 
        # user 2 should be able to apply voucher
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        cart_2.apply_voucher(voucher.code)
 
        cart_2.cart.active = False
 
        cart_2.cart.save()
 

	
 
        cart_2.next_cart()
 

	
 
        # After the reservation duration, even though the voucher has applied,
 
        # it exceeds the number of vouchers available.
 
        self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
 
        with self.assertRaises(ValidationError):
 
            cart_1.validate_cart()
 

	
 
    def test_fix_simple_errors_resolves_unavailable_voucher(self):
 
        self.test_apply_voucher()
 

	
 
        # User has an exhausted voucher leftover from test_apply_voucher
 
        cart_1 = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            cart_1.validate_cart()
 

	
 
        cart_1.fix_simple_errors()
 
        # This should work now.
 
        cart_1.validate_cart()
 

	
 
    def test_voucher_enables_item(self):
 
        voucher = self.new_voucher()
 

	
 
        enabling_condition = rego.VoucherEnablingCondition.objects.create(
 
            description="Voucher condition",
 
            voucher=voucher,
 
            mandatory=False,
 
        )
 
        enabling_condition.save()
 
        enabling_condition.products.add(self.PROD_1)
 
        enabling_condition.save()
 

	
 
        # Adding the product without a voucher will not work
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        # Apply the voucher
 
        current_cart.apply_voucher(voucher.code)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
    def test_voucher_enables_discount(self):
 
        voucher = self.new_voucher()
 

	
 
        discount = rego.VoucherDiscount.objects.create(
 
            description="VOUCHER RECIPIENT",
 
            voucher=voucher,
 
        )
 
        discount.save()
 
        rego.DiscountForProduct.objects.create(
 
            discount=discount,
 
            product=self.PROD_1,
 
            percentage=Decimal(100),
 
            quantity=1
 
        ).save()
 

	
 
        # Having PROD_1 in place should add a discount
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 
        self.assertEqual(1, len(current_cart.cart.discountitem_set.all()))
 

	
 
    def test_voucher_codes_unique(self):
 
        self.new_voucher(code="VOUCHER")
 
        with self.assertRaises(IntegrityError):
 
            self.new_voucher(code="VOUCHER")
 

	
 
    def test_multiple_vouchers_work(self):
 
        self.new_voucher(code="VOUCHER1")
 
        self.new_voucher(code="VOUCHER2")
 

	
 
    def test_vouchers_case_insensitive(self):
 
        voucher = self.new_voucher(code="VOUCHeR")
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code.lower())
 

	
 
    def test_voucher_can_only_be_applied_once(self):
 
        voucher = self.new_voucher(limit=2)
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
        # You can apply the code twice, but it will only add to the cart once.
 
        self.assertEqual(1, current_cart.cart.vouchers.count())
 

	
 
    def test_voucher_can_only_be_applied_once_across_multiple_carts(self):
 
        voucher = self.new_voucher(limit=2)
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
        inv = InvoiceController.for_cart(current_cart.cart)
 
        inv.pay("Hello!", inv.invoice.value)
 

	
 
        current_cart.next_cart()
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 

	
 
        with self.assertRaises(ValidationError):
 
            current_cart.apply_voucher(voucher.code)
 

	
 
        return current_cart
 

	
 
    def test_refund_releases_used_vouchers(self):
 
        voucher = self.new_voucher(limit=2)
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 
        current_cart.add_to_cart(self.PROD_1, 1)
 

	
 
        inv = InvoiceController.for_cart(current_cart.cart)
 
        inv.pay("Hello!", inv.invoice.value)
 
        if not inv.invoice.paid:
 
            inv.pay("Hello!", inv.invoice.value)
 

	
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        with self.assertRaises(ValidationError):
 
            current_cart.apply_voucher(voucher.code)
 

	
 
        inv.refund("Hello!", inv.invoice.value)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
    def test_fix_simple_errors_does_not_remove_limited_voucher(self):
 
        voucher = self.new_voucher(code="VOUCHER")
 
        current_cart = TestingCartController.for_user(self.USER_1)
 
        current_cart.apply_voucher(voucher.code)
 

	
 
        current_cart.fix_simple_errors()
 
        self.assertEqual(1, current_cart.cart.vouchers.count())
0 comments (0 inline, 0 general)