From cc424908321921582f5b6db50b155a7271e6f061 Mon Sep 17 00:00:00 2001
From: Christopher Neugebauer <chrisjrn@gmail.com>
Date: Wed, 23 Mar 2016 13:29:18 +1100
Subject: [PATCH] Applying a voucher to a cart now uses the voucher code rather
 than the voucher object. Adds tests for constraints on vouchers.

---
 registrasion/controllers/cart.py   |  8 ++++++--
 registrasion/models.py             |  5 +++++
 registrasion/tests/test_invoice.py |  2 +-
 registrasion/tests/test_voucher.py | 31 ++++++++++++++++++++++--------
 4 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/registrasion/controllers/cart.py b/registrasion/controllers/cart.py
index 5cbe5c8c..59b308ed 100644
--- a/registrasion/controllers/cart.py
+++ b/registrasion/controllers/cart.py
@@ -121,13 +121,17 @@ class CartController(object):
             old_quantity = 0
         self.set_quantity(product, old_quantity + quantity)
 
-    def apply_voucher(self, voucher):
-        ''' Applies the given voucher to this cart. '''
+    def apply_voucher(self, voucher_code):
+        ''' Applies the voucher with the given code to this cart. '''
 
         # TODO: is it valid for a cart to re-add a voucher that they have?
 
         # Is voucher exhausted?
         active_carts = rego.Cart.reserved_carts()
+
+        # Try and find the voucher
+        voucher = rego.Voucher.objects.get(code=voucher_code.upper())
+
         carts_with_voucher = active_carts.filter(vouchers=voucher)
         if len(carts_with_voucher) >= voucher.limit:
             raise ValidationError("This voucher is no longer available")
diff --git a/registrasion/models.py b/registrasion/models.py
index c84a6abf..96ecf219 100644
--- a/registrasion/models.py
+++ b/registrasion/models.py
@@ -99,6 +99,11 @@ class Voucher(models.Model):
     def __str__(self):
         return "Voucher for %s" % self.recipient
 
+    def save(self, *a, **k):
+        ''' Normalise the voucher code to be uppercase '''
+        self.code = self.code.upper()
+        super(Voucher, self).save(*a, **k)
+
     recipient = models.CharField(max_length=64, verbose_name=_("Recipient"))
     code = models.CharField(max_length=16,
                             unique=True,
diff --git a/registrasion/tests/test_invoice.py b/registrasion/tests/test_invoice.py
index 4d0360ce..637e82db 100644
--- a/registrasion/tests/test_invoice.py
+++ b/registrasion/tests/test_invoice.py
@@ -91,7 +91,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
         ).save()
 
         current_cart = CartController.for_user(self.USER_1)
-        current_cart.apply_voucher(voucher)
+        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)
diff --git a/registrasion/tests/test_voucher.py b/registrasion/tests/test_voucher.py
index 169a1b79..50b83417 100644
--- a/registrasion/tests/test_voucher.py
+++ b/registrasion/tests/test_voucher.py
@@ -3,6 +3,7 @@ import pytz
 
 from decimal import Decimal
 from django.core.exceptions import ValidationError
+from django.db import IntegrityError
 
 from registrasion import models as rego
 from registrasion.controllers.cart import CartController
@@ -15,10 +16,10 @@ UTC = pytz.timezone('UTC')
 class VoucherTestCases(RegistrationCartTestCase):
 
     @classmethod
-    def new_voucher(self):
+    def new_voucher(self, code="VOUCHER"):
         voucher = rego.Voucher.objects.create(
             recipient="Voucher recipient",
-            code="VOUCHER",
+            code=code,
             limit=1
         )
         voucher.save()
@@ -30,18 +31,18 @@ class VoucherTestCases(RegistrationCartTestCase):
         self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
 
         cart_1 = CartController.for_user(self.USER_1)
-        cart_1.apply_voucher(voucher)
+        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 = CartController.for_user(self.USER_2)
         with self.assertRaises(ValidationError):
-            cart_2.apply_voucher(voucher)
+            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)
+        cart_2.apply_voucher(voucher.code)
         cart_2.cart.active = False
         cart_2.cart.save()
 
@@ -49,7 +50,7 @@ class VoucherTestCases(RegistrationCartTestCase):
         # voucher, as user 2 has paid for their cart.
         self.add_timedelta(rego.Voucher.RESERVATION_DURATION * 2)
         with self.assertRaises(ValidationError):
-            cart_1.apply_voucher(voucher)
+            cart_1.apply_voucher(voucher.code)
 
     def test_voucher_enables_item(self):
         voucher = self.new_voucher()
@@ -69,7 +70,7 @@ class VoucherTestCases(RegistrationCartTestCase):
             current_cart.add_to_cart(self.PROD_1, 1)
 
         # Apply the voucher
-        current_cart.apply_voucher(voucher)
+        current_cart.apply_voucher(voucher.code)
         current_cart.add_to_cart(self.PROD_1, 1)
 
     def test_voucher_enables_discount(self):
@@ -89,6 +90,20 @@ class VoucherTestCases(RegistrationCartTestCase):
 
         # Having PROD_1 in place should add a discount
         current_cart = CartController.for_user(self.USER_1)
-        current_cart.apply_voucher(voucher)
+        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):
+        voucher1 = self.new_voucher(code="VOUCHER")
+        with self.assertRaises(IntegrityError):
+            voucher2 = self.new_voucher(code="VOUCHER")
+
+    def test_multiple_vouchers_work(self):
+        voucher1 = self.new_voucher(code="VOUCHER1")
+        voucher2 = self.new_voucher(code="VOUCHER2")
+
+    def test_vouchers_case_insensitive(self):
+        voucher = self.new_voucher(code="VOUCHeR")
+        current_cart = CartController.for_user(self.USER_1)
+        current_cart.apply_voucher(voucher.code.lower())