diff --git a/README.rst b/README.rst
index 630b8bb..c5dcb99 100644
--- a/README.rst
+++ b/README.rst
@@ -30,6 +30,8 @@ Getting Started
To get started with Sharpy, simply install it like you would any other python
package
+.. code::
+
pip install sharpy
Optionally, you can also install `lxml `_ on your
@@ -50,8 +52,19 @@ Code
You can checkout and download Sharpy's latest code at `Github
`_.
+Installing elementtree for Development and Unit Testing
+=======================================================
+When trying to install elementtree, pip may report that there is no such package. If this happens to you, you can work around by downloading and installing it manually.
+
+.. code::
+
+ wget http://effbot.org/media/downloads/elementtree-1.2.6-20050316.zip
+ unzip elementtree-1.2.6-20050316.zip
+ cd elementtree-1.2.6-20050316/
+ pip install .
+
TODOs
=====
* Flesh out the documentation to cover the full API.
-* Add support for the various filtering options in the `get_customers` call.
\ No newline at end of file
+* Add support for the various filtering options in the `get_customers` call.
diff --git a/setup.py b/setup.py
index fd44c3d..ce2c96b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
try:
- from setuptools import setup
+ from setuptools import setup
except ImportError, err:
from distutils.core import setup
diff --git a/sharpy/__init__.py b/sharpy/__init__.py
index 1bc90e9..c07c373 100644
--- a/sharpy/__init__.py
+++ b/sharpy/__init__.py
@@ -1 +1 @@
-VERSION = (0, 8)
+VERSION = (0, 9)
diff --git a/sharpy/client.py b/sharpy/client.py
index 88f940f..980d204 100644
--- a/sharpy/client.py
+++ b/sharpy/client.py
@@ -1,17 +1,26 @@
import base64
import logging
from urllib import urlencode
-from decimal import getcontext
from dateutil.tz import tzutc
import httplib2
-from sharpy.exceptions import CheddarError, AccessDenied, BadRequest, NotFound, PreconditionFailed, CheddarFailure, NaughtyGateway, UnprocessableEntity
+from sharpy.exceptions import AccessDenied
+from sharpy.exceptions import BadRequest
+from sharpy.exceptions import CheddarError
+from sharpy.exceptions import CheddarFailure
+from sharpy.exceptions import NaughtyGateway
+from sharpy.exceptions import NotFound
+from sharpy.exceptions import PreconditionFailed
+from sharpy.exceptions import UnprocessableEntity
client_log = logging.getLogger('SharpyClient')
+
class Client(object):
default_endpoint = 'https://cheddargetter.com/xml'
- def __init__(self, username, password, product_code, cache=None, timeout=None, endpoint=None):
+
+ def __init__(self, username, password, product_code, cache=None,
+ timeout=None, endpoint=None):
'''
username - Your cheddargetter username (probably an email address)
password - Your cheddargetter password
@@ -83,7 +92,8 @@ def make_request(self, path, params=None, data=None, method=None):
method = 'POST'
body = urlencode(data)
headers = {
- 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'content-type':
+ 'application/x-www-form-urlencoded; charset=UTF-8',
}
client_log.debug('Request Method: %s' % method)
@@ -92,10 +102,10 @@ def make_request(self, path, params=None, data=None, method=None):
# Setup http client
h = httplib2.Http(cache=self.cache, timeout=self.timeout)
- #h.add_credentials(self.username, self.password)
- # Skip the normal http client behavior and send auth headers immediately
- # to save an http request.
- headers['Authorization'] = "Basic %s" % base64.standard_b64encode(self.username + ':' + self.password).strip()
+ # Skip the normal http client behavior and send auth headers
+ # immediately to save an http request.
+ headers['Authorization'] = "Basic %s" % base64.standard_b64encode(
+ self.username + ':' + self.password).strip()
# Make request
response, content = h.request(url, method, body=body, headers=headers)
@@ -123,5 +133,3 @@ def make_request(self, path, params=None, data=None, method=None):
response.content = content
return response
-
-
diff --git a/sharpy/exceptions.py b/sharpy/exceptions.py
index 4841dae..ab2cf17 100644
--- a/sharpy/exceptions.py
+++ b/sharpy/exceptions.py
@@ -2,16 +2,16 @@
class CheddarError(Exception):
"Base class for exceptions returned by cheddar"
-
+
def __init__(self, response, content, *args, **kwargs):
# Importing in method to break circular dependecy
from sharpy.parsers import parse_error
-
+
super(CheddarError, self).__init__(*args, **kwargs)
error_info = parse_error(content)
self.response = response
self.error_info = error_info
-
+
def __str__(self):
return '%s (%s) %s - %s' % (
self.response.status,
@@ -20,42 +20,50 @@ def __str__(self):
self.error_info['message'],
)
+
class AccessDenied(CheddarError):
"A request to cheddar returned a status code of 401"
pass
-
+
+
class BadRequest(CheddarError):
"A request to cheddar was invalid in some way"
pass
+
class NotFound(CheddarError):
"A request to chedder was made for a resource which doesn't exist"
pass
-
+
+
class CheddarFailure(CheddarError):
"A request to cheddar encountered an unexpected error on the cheddar side"
pass
-
+
+
class PreconditionFailed(CheddarError):
"A request to cheddar was made but failed CG's validation in some way."
pass
-
+
+
class NaughtyGateway(CheddarError):
"""
Cheddar either couldn't contact the gateway or the gateway did something
very unexpected.
"""
pass
-
+
+
class UnprocessableEntity(CheddarError):
"""
An error occurred during processing. Please fix the error and try again.
"""
pass
-
+
+
class ParseError(Exception):
"""
Sharpy recieved unknown output from cheddar and doesn't know what
to do with it.
"""
- pass
\ No newline at end of file
+ pass
diff --git a/sharpy/parsers.py b/sharpy/parsers.py
index 6d4b417..846fcd7 100644
--- a/sharpy/parsers.py
+++ b/sharpy/parsers.py
@@ -1,4 +1,3 @@
-from datetime import datetime, date
from decimal import Decimal, InvalidOperation
import logging
@@ -8,11 +7,12 @@
from lxml.etree import XML
except ImportError:
from elementtree.ElementTree import XML
-
+
from sharpy.exceptions import ParseError
client_log = logging.getLogger('SharpyClient')
+
def parse_error(xml_str):
error = {}
doc = XML(xml_str)
@@ -27,9 +27,9 @@ def parse_error(xml_str):
error['code'] = elem.attrib['code']
error['aux_code'] = elem.attrib['auxCode']
error['message'] = elem.text
-
+
return error
-
+
class CheddarOutputParser(object):
'''
@@ -45,9 +45,9 @@ def parse_bool(self, content):
value = False
else:
raise ParseError("Can't parse '%s' as a bool." % content)
-
+
return value
-
+
def parse_int(self, content):
value = None
if content != '' and content is not None:
@@ -55,9 +55,9 @@ def parse_int(self, content):
value = int(content)
except ValueError:
raise ParseError("Can't parse '%s' as an int." % content)
-
+
return value
-
+
def parse_decimal(self, content):
value = None
if content != '' and content is not None:
@@ -65,9 +65,9 @@ def parse_decimal(self, content):
value = Decimal(content)
except InvalidOperation:
raise ParseError("Can't parse '%s' as a decimal." % content)
-
+
return value
-
+
def parse_datetime(self, content):
value = None
if content:
@@ -75,10 +75,10 @@ def parse_datetime(self, content):
value = date_parser.parse(content)
except ValueError:
raise ParseError("Can't parse '%s' as a datetime." % content)
-
-
+
return value
-
+
+
class PlansParser(CheddarOutputParser):
'''
A utility class for parsing cheddar's xml output for pricing plans.
@@ -89,9 +89,9 @@ def parse_xml(self, xml_str):
for plan_xml in plans_xml:
plan = self.parse_plan(plan_xml)
plans.append(plan)
-
+
return plans
-
+
def parse_plan(self, plan_element):
plan = {}
plan['id'] = plan_element.attrib['id']
@@ -101,45 +101,58 @@ def parse_plan(self, plan_element):
plan['is_active'] = self.parse_bool(plan_element.findtext('isActive'))
plan['is_free'] = self.parse_bool(plan_element.findtext('isFree'))
plan['trial_days'] = self.parse_int(plan_element.findtext('trialDays'))
- plan['initial_bill_count'] = self.parse_int(plan_element.findtext('initialBillCount'))
- plan['initial_bill_count_unit'] = plan_element.findtext('initialBillCountUnit')
+ plan['initial_bill_count'] = self.parse_int(plan_element.findtext(
+ 'initialBillCount'))
+ plan['initial_bill_count_unit'] = plan_element.findtext(
+ 'initialBillCountUnit')
plan['billing_frequency'] = plan_element.findtext('billingFrequency')
- plan['billing_frequency_per'] = plan_element.findtext('billingFrequencyPer')
- plan['billing_frequency_unit'] = plan_element.findtext('billingFrequencyUnit')
- plan['billing_frequency_quantity'] = self.parse_int(plan_element.findtext('billingFrequencyQuantity'))
+ plan['billing_frequency_per'] = plan_element.findtext(
+ 'billingFrequencyPer')
+ plan['billing_frequency_unit'] = plan_element.findtext(
+ 'billingFrequencyUnit')
+ plan['billing_frequency_quantity'] = self.parse_int(
+ plan_element.findtext('billingFrequencyQuantity'))
plan['setup_charge_code'] = plan_element.findtext('setupChargeCode')
- plan['setup_charge_amount'] = self.parse_decimal(plan_element.findtext('setupChargeAmount'))
- plan['recurring_charge_code'] = plan_element.findtext('recurringChargeCode')
- plan['recurring_charge_amount'] = self.parse_decimal(plan_element.findtext('recurringChargeAmount'))
- plan['created_datetime'] = self.parse_datetime(plan_element.findtext('createdDatetime'))
-
+ plan['setup_charge_amount'] = self.parse_decimal(
+ plan_element.findtext('setupChargeAmount'))
+ plan['recurring_charge_code'] = plan_element.findtext(
+ 'recurringChargeCode')
+ plan['recurring_charge_amount'] = self.parse_decimal(
+ plan_element.findtext('recurringChargeAmount'))
+ plan['created_datetime'] = self.parse_datetime(
+ plan_element.findtext('createdDatetime'))
+
plan['items'] = self.parse_plan_items(plan_element.find('items'))
-
+
return plan
-
+
def parse_plan_items(self, items_element):
items = []
-
+
if items_element is not None:
for item_element in items_element:
items.append(self.parse_plan_item(item_element))
-
+
return items
-
+
def parse_plan_item(self, item_element):
item = {}
-
+
item['id'] = item_element.attrib['id']
item['code'] = item_element.attrib['code']
item['name'] = item_element.findtext('name')
- item['quantity_included'] = self.parse_decimal(item_element.findtext('quantityIncluded'))
- item['is_periodic'] = self.parse_bool(item_element.findtext('isPeriodic'))
- item['overage_amount'] = self.parse_decimal(item_element.findtext('overageAmount'))
- item['created_datetime'] = self.parse_datetime(item_element.findtext('createdDatetime'))
-
+ item['quantity_included'] = self.parse_decimal(
+ item_element.findtext('quantityIncluded'))
+ item['is_periodic'] = self.parse_bool(
+ item_element.findtext('isPeriodic'))
+ item['overage_amount'] = self.parse_decimal(
+ item_element.findtext('overageAmount'))
+ item['created_datetime'] = self.parse_datetime(
+ item_element.findtext('createdDatetime'))
+
return item
-
-
+
+
class CustomersParser(CheddarOutputParser):
'''
Utility class for parsing cheddar's xml output for customers.
@@ -150,12 +163,12 @@ def parse_xml(self, xml_str):
for customer_xml in customers_xml:
customer = self.parse_customer(customer_xml)
customers.append(customer)
-
+
return customers
-
+
def parse_customer(self, customer_element):
customer = {}
-
+
# Basic info
customer['id'] = customer_element.attrib['id']
customer['code'] = customer_element.attrib['code']
@@ -167,60 +180,72 @@ def parse_customer(self, customer_element):
customer['gateway_token'] = customer_element.findtext('gateway_token')
customer['is_vat_exempt'] = customer_element.findtext('isVatExempt')
customer['vat_number'] = customer_element.findtext('vatNumber')
- customer['first_contact_datetime'] = self.parse_datetime(customer_element.findtext('firstContactDatetime'))
+ customer['first_contact_datetime'] = self.parse_datetime(
+ customer_element.findtext('firstContactDatetime'))
customer['referer'] = customer_element.findtext('referer')
customer['referer_host'] = customer_element.findtext('refererHost')
- customer['campaign_source'] = customer_element.findtext('campaignSource')
- customer['campaign_medium'] = customer_element.findtext('campaignMedium')
+ customer['campaign_source'] = customer_element.findtext(
+ 'campaignSource')
+ customer['campaign_medium'] = customer_element.findtext(
+ 'campaignMedium')
customer['campaign_term'] = customer_element.findtext('campaignTerm')
- customer['campaign_content'] = customer_element.findtext('campaignContent')
+ customer['campaign_content'] = customer_element.findtext(
+ 'campaignContent')
customer['campaign_name'] = customer_element.findtext('campaignName')
- customer['created_datetime'] = self.parse_datetime(customer_element.findtext('createdDatetime'))
- customer['modified_datetime'] = self.parse_datetime(customer_element.findtext('modifiedDatetime'))
-
+ customer['created_datetime'] = self.parse_datetime(
+ customer_element.findtext('createdDatetime'))
+ customer['modified_datetime'] = self.parse_datetime(
+ customer_element.findtext('modifiedDatetime'))
+
# Metadata
- customer['meta_data'] = self.parse_meta_data(customer_element.find('metaData'))
-
+ customer['meta_data'] = self.parse_meta_data(
+ customer_element.find('metaData'))
+
# Subscriptions
- customer['subscriptions'] = self.parse_subscriptions(customer_element.find('subscriptions'))
-
+ customer['subscriptions'] = self.parse_subscriptions(
+ customer_element.find('subscriptions'))
+
return customer
-
+
def parse_meta_data(self, meta_data_element):
meta_data = []
for meta_datum_element in meta_data_element:
meta_data.append(self.parse_meta_datum(meta_datum_element))
-
+
return meta_data
-
+
def parse_meta_datum(self, meta_datum_element):
meta_datum = {}
-
+
meta_datum['id'] = meta_datum_element.attrib['id']
meta_datum['name'] = meta_datum_element.findtext('name')
meta_datum['value'] = meta_datum_element.findtext('value')
- meta_datum['created_datetime'] = self.parse_datetime(meta_datum_element.findtext('createdDatetime'))
- meta_datum['modified_datetime'] = self.parse_datetime(meta_datum_element.findtext('modifiedDatetime'))
-
+ meta_datum['created_datetime'] = self.parse_datetime(
+ meta_datum_element.findtext('createdDatetime'))
+ meta_datum['modified_datetime'] = self.parse_datetime(
+ meta_datum_element.findtext('modifiedDatetime'))
+
return meta_datum
-
-
+
def parse_subscriptions(self, subscriptions_element):
subscriptions = []
for subscription_element in subscriptions_element:
subscription = self.parse_subscription(subscription_element)
subscriptions.append(subscription)
-
+
return subscriptions
-
+
def parse_subscription(self, subscription_element):
subscription = {}
-
+
# Basic info
subscription['id'] = subscription_element.attrib['id']
- subscription['gateway_token'] = subscription_element.findtext('gatewayToken')
- subscription['cc_first_name'] = subscription_element.findtext('ccFirstName')
- subscription['cc_last_name'] = subscription_element.findtext('ccLastName')
+ subscription['gateway_token'] = subscription_element.findtext(
+ 'gatewayToken')
+ subscription['cc_first_name'] = subscription_element.findtext(
+ 'ccFirstName')
+ subscription['cc_last_name'] = subscription_element.findtext(
+ 'ccLastName')
subscription['cc_company'] = subscription_element.findtext('ccCompany')
subscription['cc_country'] = subscription_element.findtext('ccCountry')
subscription['cc_address'] = subscription_element.findtext('ccAddress')
@@ -229,12 +254,20 @@ def parse_subscription(self, subscription_element):
subscription['cc_zip'] = subscription_element.findtext('ccZip')
subscription['cc_type'] = subscription_element.findtext('ccType')
subscription['cc_email'] = subscription_element.findtext('ccEmail')
- subscription['cc_last_four'] = subscription_element.findtext('ccLastFour')
- subscription['cc_expiration_date'] = subscription_element.findtext('ccExpirationDate')
- subscription['cancel_type'] = subscription_element.findtext('cancelType')
- subscription['cancel_reason'] = subscription_element.findtext('cancelReason')
- subscription['canceled_datetime'] = self.parse_datetime(subscription_element.findtext('canceledDatetime'))
- subscription['created_datetime'] = self.parse_datetime(subscription_element.findtext('createdDatetime'))
+ subscription['cc_last_four'] = subscription_element.findtext(
+ 'ccLastFour')
+ subscription['cc_expiration_date'] = subscription_element.findtext(
+ 'ccExpirationDate')
+ subscription['cancel_type'] = subscription_element.findtext(
+ 'cancelType')
+ subscription['cancel_reason'] = subscription_element.findtext(
+ 'cancelReason')
+ subscription['canceled_datetime'] = self.parse_datetime(
+ subscription_element.findtext('canceledDatetime'))
+ subscription['created_datetime'] = self.parse_datetime(
+ subscription_element.findtext('createdDatetime'))
+ subscription['coupon_code'] = subscription_element.findtext(
+ 'couponCode')
gateway_account_element = subscription_element.find('gatewayAccount')
if gateway_account_element is not None:
subscription['gateway_account'] = {
@@ -242,90 +275,175 @@ def parse_subscription(self, subscription_element):
'gateway': gateway_account_element.findtext('gateway'),
'type': gateway_account_element.findtext('type')
}
- subscription['redirect_url'] = subscription_element.findtext('redirectUrl')
-
+ subscription['redirect_url'] = subscription_element.findtext(
+ 'redirectUrl')
+
# Plans
- subscription['plans'] = self.parse_plans(subscription_element.find('plans'))
-
+ subscription['plans'] = self.parse_plans(
+ subscription_element.find('plans'))
+
# Invoices
- subscription['invoices'] = self.parse_invoices(subscription_element.find('invoices'))
-
- subscription['items'] = self.parse_subscription_items(subscription_element.find('items'))
-
+ subscription['invoices'] = self.parse_invoices(
+ subscription_element.find('invoices'))
+
+ subscription['items'] = self.parse_subscription_items(
+ subscription_element.find('items'))
+
return subscription
-
+
def parse_plans(self, plans_element):
plans_parser = PlansParser()
plans = []
-
+
if plans_element is not None:
for plan_element in plans_element:
plans.append(plans_parser.parse_plan(plan_element))
-
+
return plans
-
+
def parse_invoices(self, invoices_element):
invoices = []
if invoices_element is not None:
for invoice_element in invoices_element:
invoices.append(self.parse_invoice(invoice_element))
-
+
return invoices
-
+
def parse_invoice(self, invoice_element):
invoice = {}
-
+
invoice['id'] = invoice_element.attrib['id']
invoice['number'] = invoice_element.findtext('number')
invoice['type'] = invoice_element.findtext('type')
invoice['vat_rate'] = invoice_element.findtext('vatRate')
- invoice['billing_datetime'] = self.parse_datetime(invoice_element.findtext('billingDatetime'))
- invoice['paid_transaction_id'] = invoice_element.findtext('paidTransactionId')
- invoice['created_datetime'] = self.parse_datetime(invoice_element.findtext('createdDatetime'))
-
- invoice['charges'] = self.parse_charges(invoice_element.find('charges'))
-
+ invoice['billing_datetime'] = self.parse_datetime(
+ invoice_element.findtext('billingDatetime'))
+ invoice['paid_transaction_id'] = invoice_element.findtext(
+ 'paidTransactionId')
+ invoice['created_datetime'] = self.parse_datetime(
+ invoice_element.findtext('createdDatetime'))
+
+ invoice['charges'] = self.parse_charges(
+ invoice_element.find('charges'))
+
return invoice
-
+
def parse_charges(self, charges_element):
charges = []
-
+
for charge_element in charges_element:
charges.append(self.parse_charge(charge_element))
-
+
return charges
-
+
def parse_charge(self, charge_element):
charge = {}
-
+
charge['id'] = charge_element.attrib['id']
charge['code'] = charge_element.attrib['code']
charge['type'] = charge_element.findtext('type')
- charge['quantity'] = self.parse_decimal(charge_element.findtext('quantity'))
- charge['each_amount'] = self.parse_decimal(charge_element.findtext('eachAmount'))
+ charge['quantity'] = self.parse_decimal(
+ charge_element.findtext('quantity'))
+ charge['each_amount'] = self.parse_decimal(
+ charge_element.findtext('eachAmount'))
charge['description'] = charge_element.findtext('description')
- charge['created_datetime'] = self.parse_datetime(charge_element.findtext('createdDatetime'))
-
+ charge['created_datetime'] = self.parse_datetime(
+ charge_element.findtext('createdDatetime'))
+
return charge
-
+
def parse_subscription_items(self, items_element):
items = []
-
+
if items_element is not None:
for item_element in items_element:
items.append(self.parse_subscription_item(item_element))
-
+
return items
-
+
def parse_subscription_item(self, item_element):
item = {}
item['id'] = item_element.attrib['id']
item['code'] = item_element.attrib['code']
item['name'] = item_element.findtext('name')
- item['quantity'] = self.parse_decimal(item_element.findtext('quantity'))
- item['created_datetime'] = self.parse_datetime(item_element.findtext('createdDatetime'))
- item['modified_datetime'] = self.parse_datetime(item_element.findtext('modifiedDatetime'))
-
+ item['quantity'] = self.parse_decimal(
+ item_element.findtext('quantity'))
+ item['created_datetime'] = self.parse_datetime(
+ item_element.findtext('createdDatetime'))
+ item['modified_datetime'] = self.parse_datetime(
+ item_element.findtext('modifiedDatetime'))
+
return item
-
+
+
+class PromotionsParser(CheddarOutputParser):
+ '''
+ A utility class for parsing cheddar's xml output for promotions.
+ '''
+ def parse_xml(self, xml_str):
+ promotions = []
+ promotions_xml = XML(xml_str)
+ for promotion_xml in promotions_xml:
+ promotion = self.parse_promotion(promotion_xml)
+ promotions.append(promotion)
+
+ return promotions
+
+ def parse_promotion(self, promotion_element):
+ promotion = {}
+ promotion['id'] = promotion_element.attrib['id']
+ promotion['name'] = promotion_element.findtext('name')
+ promotion['description'] = promotion_element.findtext('description')
+ promotion['created_datetime'] = self.parse_datetime(
+ promotion_element.findtext('createdDatetime'))
+
+ promotion['incentives'] = self.parse_incentives(
+ promotion_element.find('incentives'))
+ promotion['coupons'] = self.parse_coupons(
+ promotion_element.find('coupons'))
+
+ return promotion
+
+ def parse_incentives(self, incentives_element):
+ incentives = []
+
+ if incentives_element is not None:
+ for incentive_element in incentives_element:
+ incentives.append(self.parse_incentive(incentive_element))
+
+ return incentives
+
+ def parse_incentive(self, incentive_element):
+ incentive = {}
+
+ incentive['id'] = incentive_element.attrib['id']
+ incentive['type'] = incentive_element.findtext('type')
+ incentive['percentage'] = incentive_element.findtext('percentage')
+ incentive['months'] = incentive_element.findtext('months')
+ incentive['created_datetime'] = self.parse_datetime(
+ incentive_element.findtext('createdDatetime'))
+
+ return incentive
+
+ def parse_coupons(self, coupons_element):
+ coupons = []
+
+ if coupons_element is not None:
+ for coupon_element in coupons_element:
+ coupons.append(self.parse_coupon(coupon_element))
+
+ return coupons
+
+ def parse_coupon(self, coupon_element):
+ coupon = {}
+
+ coupon['id'] = coupon_element.attrib['id']
+ coupon['code'] = coupon_element.attrib['code']
+ coupon['max_redemptions'] = coupon_element.findtext('maxRedemptions')
+ coupon['expiration_datetime'] = self.parse_datetime(
+ coupon_element.findtext('expirationDatetime'))
+ coupon['created_datetime'] = self.parse_datetime(
+ coupon_element.findtext('createdDatetime'))
+
+ return coupon
diff --git a/sharpy/product.py b/sharpy/product.py
index cd6c0bc..188b80a 100644
--- a/sharpy/product.py
+++ b/sharpy/product.py
@@ -1,17 +1,19 @@
from copy import copy
-from datetime import date, datetime
-from decimal import Decimal, getcontext as get_decimal_context
+from datetime import datetime
+from decimal import Decimal
from time import time
from dateutil.relativedelta import relativedelta
from sharpy.client import Client
from sharpy.exceptions import NotFound
-from sharpy.parsers import PlansParser, CustomersParser
+from sharpy.parsers import PlansParser, CustomersParser, PromotionsParser
+
class CheddarProduct(object):
-
- def __init__(self, username, password, product_code, cache=None, timeout=None, endpoint=None):
+
+ def __init__(self, username, password, product_code, cache=None,
+ timeout=None, endpoint=None):
self.product_code = product_code
self.client = Client(
username,
@@ -21,20 +23,20 @@ def __init__(self, username, password, product_code, cache=None, timeout=None, e
timeout,
endpoint,
)
-
+
super(CheddarProduct, self).__init__()
-
+
def __repr__(self):
return u'CheddarProduct: %s' % self.product_code
-
+
def get_all_plans(self):
response = self.client.make_request(path='plans/get')
plans_parser = PlansParser()
plans_data = plans_parser.parse_xml(response.content)
plans = [PricingPlan(**plan_data) for plan_data in plans_data]
-
+
return plans
-
+
def get_plan(self, code):
response = self.client.make_request(
path='plans/get',
@@ -43,83 +45,96 @@ def get_plan(self, code):
plans_parser = PlansParser()
plans_data = plans_parser.parse_xml(response.content)
plans = [PricingPlan(**plan_data) for plan_data in plans_data]
-
+
return plans[0]
-
- def create_customer(self, code, first_name, last_name, email, plan_code, \
- company=None, is_vat_exempt=None, vat_number=None, \
- notes=None, first_contact_datetime=None, \
- referer=None, campaign_term=None, \
- campaign_name=None, campaign_source=None, \
- campaign_medium=None, campaign_content=None, \
- meta_data=None, initial_bill_date=None, method=None,\
- cc_number=None, cc_expiration=None, \
- cc_card_code=None, cc_first_name=None, \
- cc_last_name=None, cc_email=None, cc_company=None, \
- cc_country=None, cc_address=None, cc_city=None, \
- cc_state=None, cc_zip=None, return_url=None, \
- cancel_url=None, charges=None, items=None):
-
- data = self.build_customer_post_data(code, first_name, last_name, \
- email, plan_code, company, is_vat_exempt, vat_number, \
- notes, first_contact_datetime, referer, campaign_term, \
- campaign_name, campaign_source, campaign_medium, \
- campaign_content, meta_data, initial_bill_date, method, \
- cc_number, cc_expiration, cc_card_code, cc_first_name, \
- cc_last_name, cc_email, cc_company, cc_country, cc_address, \
- cc_city, cc_state, cc_zip, return_url, cancel_url)
-
+
+ def create_customer(self, code, first_name, last_name, email, plan_code,
+ company=None, is_vat_exempt=None, vat_number=None,
+ notes=None, first_contact_datetime=None,
+ referer=None, campaign_term=None,
+ campaign_name=None, campaign_source=None,
+ campaign_medium=None, campaign_content=None,
+ meta_data=None, initial_bill_date=None, method=None,
+ cc_number=None, cc_expiration=None,
+ cc_card_code=None, cc_first_name=None,
+ cc_last_name=None, cc_email=None, cc_company=None,
+ cc_country=None, cc_address=None, cc_city=None,
+ cc_state=None, cc_zip=None, coupon_code=None,
+ return_url=None, cancel_url=None, charges=None,
+ items=None):
+
+ data = self.build_customer_post_data(code, first_name, last_name,
+ email, plan_code, company,
+ is_vat_exempt, vat_number,
+ notes, first_contact_datetime,
+ referer, campaign_term,
+ campaign_name, campaign_source,
+ campaign_medium, campaign_content,
+ meta_data, initial_bill_date,
+ method, cc_number, cc_expiration,
+ cc_card_code, cc_first_name,
+ cc_last_name, cc_email,
+ cc_company, cc_country,
+ cc_address, cc_city, cc_state,
+ cc_zip, coupon_code, return_url,
+ cancel_url)
+
if charges:
for i, charge in enumerate(charges):
data['charges[%d][chargeCode]' % i] = charge['code']
data['charges[%d][quantity]' % i] = charge.get('quantity', 1)
- data['charges[%d][eachAmount]' % i] = '%.2f' % charge['each_amount']
- data['charges[%d][description]' % i] = charge.get('description', '')
+ data['charges[%d][eachAmount]' % i] = '%.2f' % (
+ charge['each_amount'])
+ data['charges[%d][description]' % i] = charge.get(
+ 'description', '')
if items:
for i, item in enumerate(items):
data['items[%d][itemCode]' % i] = item['code']
data['items[%d][quantity]' % i] = item.get('quantity', 1)
-
+
response = self.client.make_request(path='customers/new', data=data)
customer_parser = CustomersParser()
customers_data = customer_parser.parse_xml(response.content)
customer = Customer(product=self, **customers_data[0])
-
+
return customer
-
- def build_customer_post_data(self, code=None, first_name=None,\
- last_name=None, email=None, plan_code=None, \
- company=None, is_vat_exempt=None, vat_number=None, \
- notes=None, first_contact_datetime=None, \
- referer=None, campaign_term=None, \
- campaign_name=None, campaign_source=None, \
- campaign_medium=None, campaign_content=None, \
- meta_data=None, initial_bill_date=None, method=None,\
- cc_number=None, cc_expiration=None, \
- cc_card_code=None, cc_first_name=None, \
- cc_last_name=None, cc_email=None, cc_company=None, \
- cc_country=None, cc_address=None, cc_city=None, \
- cc_state=None, cc_zip=None, return_url=None, cancel_url=None, \
- bill_date=None):
+
+ def build_customer_post_data(self, code=None, first_name=None,
+ last_name=None, email=None, plan_code=None,
+ company=None, is_vat_exempt=None,
+ vat_number=None, notes=None,
+ first_contact_datetime=None, referer=None,
+ campaign_term=None, campaign_name=None,
+ campaign_source=None, campaign_medium=None,
+ campaign_content=None, meta_data=None,
+ initial_bill_date=None, method=None,
+ cc_number=None, cc_expiration=None,
+ cc_card_code=None, cc_first_name=None,
+ cc_last_name=None, cc_email=None,
+ cc_company=None, cc_country=None,
+ cc_address=None, cc_city=None,
+ cc_state=None, cc_zip=None, coupon_code=None,
+ return_url=None, cancel_url=None,
+ bill_date=None):
data = {}
-
+
if code:
data['code'] = code
-
+
if first_name:
data['firstName'] = first_name
-
+
if last_name:
data['lastName'] = last_name
-
+
if email:
data['email'] = email
-
+
if plan_code:
data['subscription[planCode]'] = plan_code
-
+
if company:
data['company'] = company
@@ -136,7 +151,8 @@ def build_customer_post_data(self, code=None, first_name=None,\
data['notes'] = notes
if first_contact_datetime:
- data['firstContactDatetime'] = self.client.format_datetime(first_contact_datetime)
+ data['firstContactDatetime'] = self.client.format_datetime(
+ first_contact_datetime)
if referer:
data['referer'] = referer
@@ -159,8 +175,9 @@ def build_customer_post_data(self, code=None, first_name=None,\
data[full_key] = value
if initial_bill_date:
- data['subscription[initialBillDate]'] = self.client.format_date(initial_bill_date)
-
+ data['subscription[initialBillDate]'] = self.client.format_date(
+ initial_bill_date)
+
if method:
data['subscription[method]'] = method
@@ -200,21 +217,25 @@ def build_customer_post_data(self, code=None, first_name=None,\
if cc_zip:
data['subscription[ccZip]'] = cc_zip
+ if coupon_code:
+ data['subscription[couponCode]'] = coupon_code
+
if return_url:
data['subscription[returnUrl]'] = return_url
-
+
if cancel_url:
data['subscription[cancelUrl]'] = cancel_url
-
+
if bill_date:
- data['subscription[changeBillDate]'] = self.client.format_datetime(bill_date)
+ data['subscription[changeBillDate]'] = self.client.format_datetime(
+ bill_date)
return data
-
+
def get_customers(self, filter_data=None):
'''
- Returns all customers. Sometimes they are too much and cause internal
- server errors on CG. API call permits post parameters for filtering
+ Returns all customers. Sometimes they are too much and cause internal
+ server errors on CG. API call permits post parameters for filtering
which tends to fix this
https://cheddargetter.com/developers#all-customers
@@ -226,81 +247,116 @@ def get_customers(self, filter_data=None):
]
'''
customers = []
-
+
try:
- response = self.client.make_request(path='customers/get', data=filter_data)
+ response = self.client.make_request(path='customers/get',
+ data=filter_data)
except NotFound:
response = None
-
+
if response:
customer_parser = CustomersParser()
customers_data = customer_parser.parse_xml(response.content)
for customer_data in customers_data:
customers.append(Customer(product=self, **customer_data))
-
+
return customers
-
+
def get_customer(self, code):
-
+
response = self.client.make_request(
path='customers/get',
params={'code': code},
)
customer_parser = CustomersParser()
customers_data = customer_parser.parse_xml(response.content)
-
+
return Customer(product=self, **customers_data[0])
-
+
def delete_all_customers(self):
'''
This method does exactly what you think it does. Calling this method
deletes all customer data in your cheddar product and the configured
gateway. This action cannot be undone.
-
+
DO NOT RUN THIS UNLESS YOU REALLY, REALLY, REALLY MEAN TO!
'''
- response = self.client.make_request(
+ self.client.make_request(
path='customers/delete-all/confirm/%d' % int(time()),
method='POST'
)
-
-
+
+ def get_all_promotions(self):
+ '''
+ Returns all promotions.
+ https://cheddargetter.com/developers#promotions
+ '''
+ promotions = []
+
+ try:
+ response = self.client.make_request(path='promotions/get')
+ except NotFound:
+ response = None
+
+ if response:
+ promotions_parser = PromotionsParser()
+ promotions_data = promotions_parser.parse_xml(response.content)
+ promotions = [Promotion(**promotion_data) for promotion_data in promotions_data]
+
+ return promotions
+
+ def get_promotion(self, code):
+ '''
+ Get the promotion with the specified coupon code.
+ https://cheddargetter.com/developers#single-promotion
+ '''
+
+ response = self.client.make_request(
+ path='promotions/get',
+ params={'code': code},
+ )
+ promotion_parser = PromotionsParser()
+ promotion_data = promotion_parser.parse_xml(response.content)
+
+ return Promotion(**promotion_data[0])
+
+
class PricingPlan(object):
-
+
def __init__(self, name, code, id, description, is_active, is_free,
- trial_days, initial_bill_count, initial_bill_count_unit,
+ trial_days, initial_bill_count, initial_bill_count_unit,
billing_frequency, billing_frequency_per,
billing_frequency_quantity, billing_frequency_unit,
setup_charge_code, setup_charge_amount,
recurring_charge_code, recurring_charge_amount,
created_datetime, items, subscription=None):
-
+
self.load_data(name=name, code=code, id=id, description=description,
- is_active=is_active, is_free=is_free,
- trial_days=trial_days,
- initial_bill_count=initial_bill_count,
- initial_bill_count_unit=initial_bill_count_unit,
- billing_frequency=billing_frequency,
- billing_frequency_per=billing_frequency_per,
- billing_frequency_quantity=billing_frequency_quantity,
- billing_frequency_unit=billing_frequency_unit,
- setup_charge_code=setup_charge_code,
- setup_charge_amount=setup_charge_amount,
- recurring_charge_code=recurring_charge_code,
- recurring_charge_amount=recurring_charge_amount,
- created_datetime=created_datetime, items=items,
- subscription=subscription)
-
+ is_active=is_active, is_free=is_free,
+ trial_days=trial_days,
+ initial_bill_count=initial_bill_count,
+ initial_bill_count_unit=initial_bill_count_unit,
+ billing_frequency=billing_frequency,
+ billing_frequency_per=billing_frequency_per,
+ billing_frequency_quantity=billing_frequency_quantity,
+ billing_frequency_unit=billing_frequency_unit,
+ setup_charge_code=setup_charge_code,
+ setup_charge_amount=setup_charge_amount,
+ recurring_charge_code=recurring_charge_code,
+ recurring_charge_amount=recurring_charge_amount,
+ created_datetime=created_datetime, items=items,
+ subscription=subscription)
+
super(PricingPlan, self).__init__()
-
+
def load_data(self, name, code, id, description, is_active, is_free,
- trial_days, initial_bill_count, initial_bill_count_unit,
- billing_frequency, billing_frequency_per,
- billing_frequency_quantity, billing_frequency_unit,
- setup_charge_code, setup_charge_amount,
- recurring_charge_code, recurring_charge_amount,
- created_datetime, items, subscription=None):
-
+ trial_days, initial_bill_count, initial_bill_count_unit,
+ billing_frequency, billing_frequency_per,
+ billing_frequency_quantity, billing_frequency_unit,
+ setup_charge_code, setup_charge_amount,
+ recurring_charge_code, recurring_charge_amount,
+ created_datetime, items, subscription=None):
+
self.name = name
self.code = code
self.id = id
@@ -323,10 +379,10 @@ def load_data(self, name, code, id, description, is_active, is_free,
if subscription:
self.subscription = subscription
-
+
def __repr__(self):
return u'PricingPlan: %s (%s)' % (self.name, self.code)
-
+
@property
def initial_bill_date(self):
'''
@@ -334,30 +390,29 @@ def initial_bill_date(self):
based on available plan info.
'''
time_to_start = None
-
+
if self.initial_bill_count_unit == 'months':
time_to_start = relativedelta(months=self.initial_bill_count)
else:
time_to_start = relativedelta(days=self.initial_bill_count)
-
+
initial_bill_date = datetime.utcnow().date() + time_to_start
-
+
return initial_bill_date
-
-
+
class Customer(object):
-
- def __init__(self, code, first_name, last_name, email, product, id=None, \
- company=None, notes=None, gateway_token=None, \
- is_vat_exempt=None, vat_number=None, \
- first_contact_datetime=None, referer=None, \
- referer_host=None, campaign_source=None, \
- campaign_medium=None, campaign_term=None, \
- campaign_content=None, campaign_name=None, \
- created_datetime=None, modified_datetime=None, \
- meta_data=None, subscriptions=None):
-
+
+ def __init__(self, code, first_name, last_name, email, product, id=None,
+ company=None, notes=None, gateway_token=None,
+ is_vat_exempt=None, vat_number=None,
+ first_contact_datetime=None, referer=None,
+ referer_host=None, campaign_source=None,
+ campaign_medium=None, campaign_term=None,
+ campaign_content=None, campaign_name=None,
+ created_datetime=None, modified_datetime=None,
+ coupon_code=None, meta_data=None, subscriptions=None):
+
self.load_data(code=code,
first_name=first_name, last_name=last_name,
email=email, product=product, id=id,
@@ -374,21 +429,20 @@ def __init__(self, code, first_name, last_name, email, product, id=None, \
campaign_name=campaign_name,
created_datetime=created_datetime,
modified_datetime=modified_datetime,
- meta_data=meta_data,
- subscriptions=subscriptions
- )
-
+ coupon_code=coupon_code, meta_data=meta_data,
+ subscriptions=subscriptions)
+
super(Customer, self).__init__()
-
- def load_data(self, code, first_name, last_name, email, product, id=None,\
- company=None, notes=None, gateway_token=None, \
- is_vat_exempt=None, vat_number=None, \
- first_contact_datetime=None, referer=None, \
- referer_host=None, campaign_source=None, \
- campaign_medium=None, campaign_term=None, \
- campaign_content=None, campaign_name=None, \
- created_datetime=None, modified_datetime=None, \
- meta_data=None, subscriptions=None):
+
+ def load_data(self, code, first_name, last_name, email, product, id=None,
+ company=None, notes=None, gateway_token=None,
+ is_vat_exempt=None, vat_number=None,
+ first_contact_datetime=None, referer=None,
+ referer_host=None, campaign_source=None,
+ campaign_medium=None, campaign_term=None,
+ campaign_content=None, campaign_name=None,
+ created_datetime=None, modified_datetime=None,
+ coupon_code=None, meta_data=None, subscriptions=None):
self.code = code
self.id = id
self.first_name = first_name
@@ -409,81 +463,76 @@ def load_data(self, code, first_name, last_name, email, product, id=None,\
self.campaign_name = campaign_name
self.created = created_datetime
self.modified = modified_datetime
+ self.coupon_code = coupon_code
self.meta_data = {}
if meta_data:
- for datum in meta_data:
- self.meta_data[datum['name']] = datum['value']
+ for datum in meta_data:
+ self.meta_data[datum['name']] = datum['value']
subscription_data = subscriptions[0]
subscription_data['customer'] = self
if hasattr(self, 'subscription'):
self.subscription.load_data(**subscription_data)
else:
self.subscription = Subscription(**subscription_data)
-
+
def load_data_from_xml(self, xml):
customer_parser = CustomersParser()
customers_data = customer_parser.parse_xml(xml)
customer_data = customers_data[0]
self.load_data(product=self.product, **customer_data)
-
- def update(self, first_name=None, last_name=None, email=None, \
- company=None, is_vat_exempt=None, vat_number=None, \
- notes=None, first_contact_datetime=None, \
- referer=None, campaign_term=None, \
- campaign_name=None, campaign_source=None, \
- campaign_medium=None, campaign_content=None, \
- meta_data=None, method=None, \
- cc_number=None, cc_expiration=None, \
- cc_card_code=None, cc_first_name=None, \
- cc_last_name=None, cc_company=None, cc_email=None,\
- cc_country=None, cc_address=None, cc_city=None, \
- cc_state=None, cc_zip=None, plan_code=None, bill_date=None,
- return_url=None, cancel_url=None,):
-
- data = self.product.build_customer_post_data( first_name=first_name,
- last_name=last_name, email=email, plan_code=plan_code,
- company=company, is_vat_exempt=is_vat_exempt,
- vat_number=vat_number, notes=notes, referer=referer,
- campaign_term=campaign_term,
- campaign_name=campaign_name,
- campaign_source=campaign_source,
- campaign_medium=campaign_medium,
- campaign_content=campaign_content,
- meta_data=meta_data, method=method,
- cc_number=cc_number, cc_expiration=cc_expiration,
- cc_card_code=cc_card_code,
- cc_first_name=cc_first_name,
- cc_last_name=cc_last_name, cc_company=cc_company,
- cc_email=cc_email, cc_country=cc_country,
- cc_address=cc_address, cc_city=cc_city,
- cc_state=cc_state, cc_zip=cc_zip, bill_date=bill_date,
- return_url=return_url, cancel_url=cancel_url,)
-
+
+ def update(self, first_name=None, last_name=None, email=None,
+ company=None, is_vat_exempt=None, vat_number=None,
+ notes=None, first_contact_datetime=None,
+ referer=None, campaign_term=None, campaign_name=None,
+ campaign_source=None, campaign_medium=None,
+ campaign_content=None, meta_data=None, method=None,
+ cc_number=None, cc_expiration=None,
+ cc_card_code=None, cc_first_name=None,
+ cc_last_name=None, cc_company=None, cc_email=None,
+ cc_country=None, cc_address=None, cc_city=None,
+ cc_state=None, cc_zip=None, plan_code=None, bill_date=None,
+ coupon_code=None, return_url=None, cancel_url=None,):
+
+ data = self.product.build_customer_post_data(
+ first_name=first_name, last_name=last_name, email=email,
+ plan_code=plan_code, company=company, is_vat_exempt=is_vat_exempt,
+ vat_number=vat_number, notes=notes, referer=referer,
+ campaign_term=campaign_term, campaign_name=campaign_name,
+ campaign_source=campaign_source, campaign_medium=campaign_medium,
+ campaign_content=campaign_content, meta_data=meta_data,
+ method=method, cc_number=cc_number, cc_expiration=cc_expiration,
+ cc_card_code=cc_card_code,
+ cc_first_name=cc_first_name, cc_last_name=cc_last_name,
+ cc_company=cc_company, cc_email=cc_email, cc_country=cc_country,
+ cc_address=cc_address, cc_city=cc_city, cc_state=cc_state,
+ cc_zip=cc_zip, bill_date=bill_date, coupon_code=coupon_code,
+ return_url=return_url, cancel_url=cancel_url,)
+
path = 'customers/edit'
params = {'code': self.code}
-
+
response = self.product.client.make_request(
- path = path,
- params = params,
- data = data,
+ path=path,
+ params=params,
+ data=data,
)
return self.load_data_from_xml(response.content)
-
-
+
def delete(self):
path = 'customers/delete'
params = {'code': self.code}
- response = self.product.client.make_request(
- path = path,
- params = params,
+ self.product.client.make_request(
+ path=path,
+ params=params,
)
-
+
def charge(self, code, each_amount, quantity=1, description=None):
'''
Add an arbitrary charge or credit to a customer's account. A positive
number will create a charge. A negative number will create a credit.
-
+
each_amount is normalized to a Decimal with a precision of 2 as that
is the level of precision which the cheddar API supports.
'''
@@ -496,7 +545,7 @@ def charge(self, code, each_amount, quantity=1, description=None):
}
if description:
data['description'] = description
-
+
response = self.product.client.make_request(
path='customers/add-charge',
params={'code': self.code},
@@ -507,14 +556,14 @@ def charge(self, code, each_amount, quantity=1, description=None):
def create_one_time_invoice(self, charges):
'''
Charges should be a list of charges to execute immediately. Each
- value in the charges diectionary should be a dictionary with the
+ value in the charges diectionary should be a dictionary with the
following keys:
code
- Your code for this charge. This code will be displayed in the
+ Your code for this charge. This code will be displayed in the
user's invoice and is limited to 36 characters.
quantity
- A positive integer quantity. If not provided this value will
+ A positive integer quantity. If not provided this value will
default to 1.
each_amount
Positive or negative integer or decimal with two digit precision.
@@ -528,7 +577,7 @@ def create_one_time_invoice(self, charges):
for n, charge in enumerate(charges):
each_amount = Decimal(charge['each_amount'])
each_amount = each_amount.quantize(Decimal('.01'))
- data['charges[%d][chargeCode]' % n ] = charge['code']
+ data['charges[%d][chargeCode]' % n] = charge['code']
data['charges[%d][quantity]' % n] = charge.get('quantity', 1)
data['charges[%d][eachAmount]' % n] = '%.2f' % each_amount
if 'description' in charge.keys():
@@ -540,49 +589,51 @@ def create_one_time_invoice(self, charges):
data=data,
)
return self.load_data_from_xml(response.content)
-
+
def __repr__(self):
return u'Customer: %s %s (%s)' % (
self.first_name,
self.last_name,
self.code
)
-
+
+
class Subscription(object):
-
+
def __init__(self, id, gateway_token, cc_first_name, cc_last_name,
cc_company, cc_country, cc_address, cc_city, cc_state,
cc_zip, cc_type, cc_last_four, cc_expiration_date, customer,
- canceled_datetime=None ,created_datetime=None,
+ canceled_datetime=None, created_datetime=None,
plans=None, invoices=None, items=None, gateway_account=None,
cancel_reason=None, cancel_type=None, cc_email=None,
- redirect_url=None):
-
- self.load_data(id=id, gateway_token=gateway_token,
- cc_first_name=cc_first_name,
- cc_last_name=cc_last_name,
- cc_company=cc_company, cc_country=cc_country,
- cc_address=cc_address, cc_city=cc_city,
- cc_state=cc_state, cc_zip=cc_zip, cc_type=cc_type,
- cc_last_four=cc_last_four,
- cc_expiration_date=cc_expiration_date, cc_email=cc_email,
- customer=customer,
- canceled_datetime=canceled_datetime,
- created_datetime=created_datetime, plans=plans,
- invoices=invoices, items=items,
- gateway_account=gateway_account,
- cancel_reason=cancel_reason, cancel_type=cancel_type,
- redirect_url=redirect_url)
-
+ coupon_code=None, redirect_url=None):
+
+ self.load_data(id=id, gateway_token=gateway_token,
+ cc_first_name=cc_first_name,
+ cc_last_name=cc_last_name,
+ cc_company=cc_company, cc_country=cc_country,
+ cc_address=cc_address, cc_city=cc_city,
+ cc_state=cc_state, cc_zip=cc_zip, cc_type=cc_type,
+ cc_last_four=cc_last_four,
+ cc_expiration_date=cc_expiration_date,
+ cc_email=cc_email, customer=customer,
+ canceled_datetime=canceled_datetime,
+ created_datetime=created_datetime, plans=plans,
+ invoices=invoices, items=items,
+ gateway_account=gateway_account,
+ cancel_reason=cancel_reason, cancel_type=cancel_type,
+ coupon_code=coupon_code, redirect_url=redirect_url)
+
super(Subscription, self).__init__()
-
- def load_data(self, id, gateway_token, cc_first_name, cc_last_name, \
- cc_company, cc_country, cc_address, cc_city, cc_state, \
- cc_zip, cc_type, cc_last_four, cc_expiration_date, customer,\
- cc_email=None, canceled_datetime=None ,created_datetime=None, \
- plans=None, invoices=None, items=None, gateway_account=None, \
- cancel_reason=None, cancel_type=None, redirect_url=None):
-
+
+ def load_data(self, id, gateway_token, cc_first_name, cc_last_name,
+ cc_company, cc_country, cc_address, cc_city, cc_state,
+ cc_zip, cc_type, cc_last_four, cc_expiration_date, customer,
+ cc_email=None, canceled_datetime=None, created_datetime=None,
+ plans=None, invoices=None, items=None, gateway_account=None,
+ cancel_reason=None, cancel_type=None, coupon_code=None,
+ redirect_url=None):
+
self.id = id
self.gateway_token = gateway_token
self.cc_first_name = cc_first_name
@@ -604,6 +655,7 @@ def load_data(self, id, gateway_token, cc_first_name, cc_last_name, \
self.gateway_account = gateway_account
self.cancel_type = cancel_type
self.cancel_reason = cancel_reason
+ self.coupon_code = coupon_code
self.redirect_url = redirect_url
# Organize item data into something more useful
@@ -613,7 +665,7 @@ def load_data(self, id, gateway_token, cc_first_name, cc_last_name, \
plan_data = plans[0]
for item in plan_data['items']:
items_map[item['code']]['plan_data'] = item
-
+
if not hasattr(self, 'items'):
self.items = {}
for code, item_map in items_map.iteritems():
@@ -622,7 +674,7 @@ def load_data(self, id, gateway_token, cc_first_name, cc_last_name, \
item_data = copy(plan_item_data)
item_data.update(subscription_item_data)
item_data['subscription'] = self
-
+
if code in self.items.keys():
item = self.items[code]
item.load_data(**item_data)
@@ -634,17 +686,17 @@ def load_data(self, id, gateway_token, cc_first_name, cc_last_name, \
self.plan.load_data(**plan_data)
else:
self.plan = PricingPlan(**plan_data)
-
+
def __repr__(self):
return u'Subscription: %s' % self.id
-
+
def cancel(self):
client = self.customer.product.client
response = client.make_request(
path='customers/cancel',
params={'code': self.customer.code},
)
-
+
customer_parser = CustomersParser()
customers_data = customer_parser.parse_xml(response.content)
customer_data = customers_data[0]
@@ -652,27 +704,28 @@ def cancel(self):
product=self.customer.product,
**customer_data
)
-
+
+
class Item(object):
-
+
def __init__(self, code, subscription, id=None, name=None,
quantity_included=None, is_periodic=None,
overage_amount=None, created_datetime=None,
modified_datetime=None, quantity=None):
-
+
self.load_data(code=code, subscription=subscription, id=id, name=name,
- quantity_included=quantity_included,
- is_periodic=is_periodic, overage_amount=overage_amount,
- created_datetime=created_datetime,
- modified_datetime=modified_datetime, quantity=quantity)
-
+ quantity_included=quantity_included,
+ is_periodic=is_periodic, overage_amount=overage_amount,
+ created_datetime=created_datetime,
+ modified_datetime=modified_datetime, quantity=quantity)
+
super(Item, self).__init__()
-
+
def load_data(self, code, subscription, id=None, name=None,
- quantity_included=None, is_periodic=None,
- overage_amount=None, created_datetime=None,
- modified_datetime=None, quantity=None):
-
+ quantity_included=None, is_periodic=None,
+ overage_amount=None, created_datetime=None,
+ modified_datetime=None, quantity=None):
+
self.code = code
self.subscription = subscription
self.id = id
@@ -683,86 +736,117 @@ def load_data(self, code, subscription, id=None, name=None,
self.overage_amount = overage_amount
self.created = created_datetime
self.modified = modified_datetime
-
+
def __repr__(self):
return u'Item: %s for %s' % (
self.code,
self.subscription.customer.code,
)
-
+
def _normalize_quantity(self, quantity=None):
if quantity is not None:
quantity = Decimal(quantity)
quantity = quantity.quantize(Decimal('.0001'))
-
+
return quantity
-
+
def increment(self, quantity=None):
'''
Increment the item's quantity by the passed in amount. If nothing is
passed in, a quantity of 1 is assumed. If a decimal value is passsed
- in, it is rounded to the 4th decimal place as that is the level of
+ in, it is rounded to the 4th decimal place as that is the level of
precision which the Cheddar API accepts.
'''
data = {}
if quantity:
data['quantity'] = self._normalize_quantity(quantity)
-
+
response = self.subscription.customer.product.client.make_request(
- path = 'customers/add-item-quantity',
- params = {
+ path='customers/add-item-quantity',
+ params={
'code': self.subscription.customer.code,
'itemCode': self.code,
},
- data = data,
- method = 'POST',
+ data=data,
+ method='POST',
)
-
+
return self.subscription.customer.load_data_from_xml(response.content)
-
+
def decrement(self, quantity=None):
'''
Decrement the item's quantity by the passed in amount. If nothing is
passed in, a quantity of 1 is assumed. If a decimal value is passsed
- in, it is rounded to the 4th decimal place as that is the level of
+ in, it is rounded to the 4th decimal place as that is the level of
precision which the Cheddar API accepts.
'''
data = {}
if quantity:
data['quantity'] = self._normalize_quantity(quantity)
-
+
response = self.subscription.customer.product.client.make_request(
- path = 'customers/remove-item-quantity',
- params = {
+ path='customers/remove-item-quantity',
+ params={
'code': self.subscription.customer.code,
'itemCode': self.code,
},
- data = data,
- method = 'POST',
+ data=data,
+ method='POST',
)
-
+
return self.subscription.customer.load_data_from_xml(response.content)
-
+
def set(self, quantity):
'''
Set the item's quantity to the passed in amount. If nothing is
passed in, a quantity of 1 is assumed. If a decimal value is passsed
- in, it is rounded to the 4th decimal place as that is the level of
+ in, it is rounded to the 4th decimal place as that is the level of
precision which the Cheddar API accepts.
'''
data = {}
data['quantity'] = self._normalize_quantity(quantity)
-
+
response = self.subscription.customer.product.client.make_request(
- path = 'customers/set-item-quantity',
- params = {
+ path='customers/set-item-quantity',
+ params={
'code': self.subscription.customer.code,
'itemCode': self.code,
},
- data = data,
- method = 'POST',
+ data=data,
+ method='POST',
)
-
+
return self.subscription.customer.load_data_from_xml(response.content)
-
-
+
+
+class Promotion(object):
+ def __init__(self, id=None, code=None, name=None, description=None,
+ created_datetime=None, incentives=None, coupons=None):
+
+ self.load_data(code=code, id=id, name=name, description=description,
+ created_datetime=created_datetime,
+ incentives=incentives, coupons=coupons)
+
+ super(Promotion, self).__init__()
+
+ def __repr__(self):
+ return u'Promotion: %s (%s)' % (self.name, self.code,)
+
+ def __unicode__(self):
+ return u'{0} ({1})'.format(self.name, self.code)
+
+ def load_data(self, id=None, code=None, name=None, description=None,
+ created_datetime=None, incentives=None, coupons=None):
+
+ self.code = code
+ self.id = id
+ self.name = name
+ self.description = description
+ self.created = created_datetime
+
+ self.incentives = incentives
+ self.coupons = coupons
+
+ # Bring coupon code up to parent promotion
+ if self.code is None and self.coupons and len(self.coupons) > 0:
+ self.code = self.coupons[0].get('code')
diff --git a/tests/client_tests.py b/tests/client_tests.py
index 571cc64..370da62 100644
--- a/tests/client_tests.py
+++ b/tests/client_tests.py
@@ -1,6 +1,5 @@
from copy import copy
from datetime import date, timedelta, datetime
-from decimal import Decimal
import unittest
from dateutil.tz import tzoffset
@@ -9,47 +8,59 @@
from testconfig import config
from sharpy.client import Client
-from sharpy.exceptions import AccessDenied, BadRequest, NotFound, CheddarFailure, NaughtyGateway, UnprocessableEntity, PreconditionFailed
+from sharpy.exceptions import AccessDenied
+from sharpy.exceptions import BadRequest
+from sharpy.exceptions import CheddarFailure
+from sharpy.exceptions import NaughtyGateway
+from sharpy.exceptions import NotFound
+from sharpy.exceptions import PreconditionFailed
+from sharpy.exceptions import UnprocessableEntity
from testing_tools.decorators import clear_users
+
class ClientTests(unittest.TestCase):
- client_defaults = {
+ client_defaults = {
'username': config['cheddar']['username'],
'password': config['cheddar']['password'],
'product_code': config['cheddar']['product_code'],
'endpoint': config['cheddar']['endpoint'],
}
-
+
def get_client(self, **kwargs):
+ ''' Helper mthod for instantiating client. '''
client_kwargs = copy(self.client_defaults)
client_kwargs.update(kwargs)
-
+
c = Client(**client_kwargs)
-
+
return c
-
+
def try_client(self, **kwargs):
+ ''' Helper method for getting client.'''
args = copy(self.client_defaults)
args.update(kwargs)
client = self.get_client(**kwargs)
-
+
self.assertEquals(args['username'], client.username)
- self.assertEquals(self.client_defaults['password'] ,client.password)
- self.assertEquals(self.client_defaults['product_code'], client.product_code)
+ self.assertEquals(self.client_defaults['password'], client.password)
+ self.assertEquals(self.client_defaults['product_code'],
+ client.product_code)
if 'endpoint' in args.keys():
self.assertEquals(args['endpoint'], client.endpoint)
else:
self.assertEquals(client.default_endpoint, client.endpoint)
-
+
def test_basic_init(self):
+ ''' Test basic initialization. '''
self.try_client()
-
+
def test_custom_endpoint_init(self):
- self.try_client(endpoint = 'http://cheddar-test.saaspire.com')
-
-
+ ''' Test initialization with custom endpoint. '''
+ self.try_client(endpoint='http://cheddar-test.saaspire.com')
+
def try_url_build(self, path, params=None):
+ ''' Helper method for client build_url method. '''
c = self.get_client()
expected = u'%s/%s/productCode/%s' % (
c.endpoint,
@@ -63,50 +74,57 @@ def try_url_build(self, path, params=None):
result = c.build_url(path=path, params=params)
self.assertEquals(expected, result)
-
+
def test_basic_build_url(self):
+ ''' Test basic client build_url. '''
path = 'users'
self.try_url_build(path)
-
-
+
def test_single_param_build_url(self):
+ ''' Test client build_url with single parameter. '''
path = 'users'
params = {'key': 'value'}
self.try_url_build(path, params)
-
+
def test_multi_param_build_url(self):
+ ''' Test client build_url with multiple parameters. '''
path = 'users'
params = {'key1': 'value1', 'key2': 'value2'}
self.try_url_build(path, params)
-
+
def test_make_request(self):
+ ''' Test client make_request method. '''
path = 'plans/get'
client = self.get_client()
response = client.make_request(path)
-
+
self.assertEquals(response.status, 200)
-
+
@raises(AccessDenied)
def test_make_request_access_denied(self):
+ ''' Test client make_request method with bad username. '''
path = 'plans/get'
bad_username = self.client_defaults['username'] + '_bad'
client = self.get_client(username=bad_username)
client.make_request(path)
-
- @raises(BadRequest)
+
+ @raises(NotFound)
def test_make_request_bad_request(self):
+ ''' Attempt to grab the plans without adding /get to the url. '''
path = 'plans'
client = self.get_client()
client.make_request(path)
-
+
@raises(NotFound)
def test_make_request_not_found(self):
+ ''' Test client make_request method with bad path. '''
path = 'things-which-dont-exist'
client = self.get_client()
client.make_request(path)
-
+
@clear_users
def test_post_request(self):
+ ''' Test client make_request method as HTTP POST. '''
path = 'customers/new'
data = {
'code': 'post_test',
@@ -117,9 +135,9 @@ def test_post_request(self):
}
client = self.get_client()
client.make_request(path, data=data)
-
- def generate_error_response(self, auxcode=None, path=None, params=None, **overrides):
+ def generate_error_response(self, auxcode=None, path=None, params=None,
+ **overrides):
'''
Creates a request to cheddar which should return an error
with the provided aux code. See the urls below for details
@@ -135,7 +153,7 @@ def generate_error_response(self, auxcode=None, path=None, params=None, **overri
zip_code = '0%d' % auxcode
else:
zip_code = '12345'
-
+
data = {
'code': 'post_test',
'firstName': 'post',
@@ -150,11 +168,13 @@ def generate_error_response(self, auxcode=None, path=None, params=None, **overri
'subscription[ccZip]': zip_code,
}
data.update(overrides)
-
+
client = self.get_client()
client.make_request(path, params=params, data=data)
-
- def assertCheddarError(self, auxcode, expected_exception, path=None, params=None):
+
+ def assertCheddarError(self, auxcode, expected_exception, path=None,
+ params=None):
+ ''' Helper method for verifing a Cheddar Error is raised. '''
assert_raises(
expected_exception,
self.generate_error_response,
@@ -162,73 +182,86 @@ def assertCheddarError(self, auxcode, expected_exception, path=None, params=None
path=path,
params=params,
)
-
+
def assertCheddarErrorForAuxCodes(self, auxcodes, expected_exception):
+ ''' Helper method for verifing a Cheddar Aux Code Error is raised. '''
for auxcode in auxcodes:
self.assertCheddarError(auxcode, expected_exception)
-
+
@clear_users
def test_cheddar_500s(self):
+ ''' Test a 500 HTTP status code on CheddarGetter. '''
auxcodes = (1000, 1002, 1003)
expected_exception = CheddarFailure
self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception)
-
+
@clear_users
def test_cheddar_400(self):
'''
The cheddar docs at
http://support.cheddargetter.com/kb/api-8/error-handling
- say that this aux code should return a 502 but in practice
+ say that this aux code should return a 502 but in practice
the API returns a 400. Not sure if this is a bug or typo in the
docs but for now we're assuming the API is correct.
'''
self.assertCheddarError(auxcode=1001, expected_exception=BadRequest)
-
+
@clear_users
def test_cheddar_401s(self):
+ ''' Test a 401 HTTP status code on CheddarGetter. '''
auxcodes = (2000, 2001, 2002, 2003)
expected_exception = AccessDenied
self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception)
-
+
@clear_users
def test_cheddar_502s(self):
+ ''' Test a 502 HTTP status code on CheddarGetter. '''
auxcodes = (3000, 4000)
expected_exception = NaughtyGateway
self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception)
-
+
@clear_users
def test_cheddar_422s(self):
+ ''' Test a 422 HTTP status code on CheddarGetter. '''
auxcodes = (5000, 5001, 5002, 5003, 6000, 6001, 6002, 7000)
expected_exception = UnprocessableEntity
self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception)
-
+
@clear_users
@raises(PreconditionFailed)
def test_cheddar_412s(self):
+ ''' Test a 412 HTTP status code on CheddarGetter. '''
self.generate_error_response(auxcode=2345, firstName='')
-
+
def test_format_datetime_with_datetime(self):
+ ''' Test client format_datetime method. '''
client = self.get_client()
- result = client.format_datetime(datetime(year=2010,month=9,day=19,hour=20,minute=10,second=39))
+ result = client.format_datetime(datetime(
+ year=2010, month=9, day=19, hour=20, minute=10, second=39))
expected = '2010-09-19T20:10:39+00:00'
self.assertEquals(expected, result)
def test_format_datetime_with_datetime_with_tz(self):
+ ''' Test client format_datetime method with timezone info. '''
client = self.get_client()
- result = client.format_datetime(datetime(year=2010,month=9,day=19,hour=20,minute=10,second=39, tzinfo=tzoffset("BRST", -10800)))
+ result = client.format_datetime(datetime(
+ year=2010, month=9, day=19, hour=20, minute=10, second=39,
+ tzinfo=tzoffset("BRST", -10800)))
expected = '2010-09-19T23:10:39+00:00'
self.assertEquals(expected, result)
def test_format_datetime_with_date(self):
+ ''' Test client format_datetime method with date. '''
client = self.get_client()
- result = client.format_datetime(date(year=2010,month=9,day=19))
+ result = client.format_datetime(date(year=2010, month=9, day=19))
expected = '2010-09-19T00:00:00+00:00'
self.assertEquals(expected, result)
-
+
def test_format_date_with_now(self):
+ ''' Test client format_date method with now. '''
client = self.get_client()
result = client.format_date('now')
expected = 'now'
@@ -236,6 +269,7 @@ def test_format_date_with_now(self):
self.assertEquals(expected, result)
def test_format_datetime_with_now(self):
+ ''' Test client format_datetime method with now. '''
client = self.get_client()
result = client.format_datetime('now')
expected = 'now'
@@ -244,7 +278,9 @@ def test_format_datetime_with_now(self):
@clear_users
def test_chedder_update_customer_error(self):
- # Overriding the zipcode so a customer actually gets created
+ '''
+ Test overriding the zipcode so a customer actually gets updated.
+ '''
overrides = {
'subscription[ccZip]': 12345
}
@@ -255,4 +291,4 @@ def test_chedder_update_customer_error(self):
expected_exception=UnprocessableEntity,
path='customers/edit',
params={'code': 'post_test'}
- )
\ No newline at end of file
+ )
diff --git a/tests/parser_tests.py b/tests/parser_tests.py
index 70835f7..ae00e68 100644
--- a/tests/parser_tests.py
+++ b/tests/parser_tests.py
@@ -7,95 +7,110 @@
from nose.tools import raises
from sharpy.exceptions import ParseError
-from sharpy.parsers import CheddarOutputParser, parse_error, PlansParser, CustomersParser
+from sharpy.parsers import CheddarOutputParser
+from sharpy.parsers import parse_error
+from sharpy.parsers import PlansParser
+from sharpy.parsers import CustomersParser
+
class ParserTests(unittest.TestCase):
-
+
def load_file(self, filename):
+ ''' Helper method to load an xml file from the files directory. '''
path = os.path.join(os.path.dirname(__file__), 'files', filename)
f = open(path)
content = f.read()
f.close()
return content
-
-
+
def test_bool_parsing_true(self):
+ ''' Test boolean parsing evaluates to true. '''
parser = CheddarOutputParser()
-
+
expected = True
result = parser.parse_bool('1')
-
+
self.assertEquals(expected, result)
-
+
def test_bool_parsing_false(self):
+ ''' Test boolean parsing evaluates to false. '''
parser = CheddarOutputParser()
-
+
expected = False
result = parser.parse_bool('0')
-
+
self.assertEquals(expected, result)
-
+
@raises(ParseError)
def test_bool_parsing_error(self):
+ ''' Test boolean parsing with non-boolean string. '''
parser = CheddarOutputParser()
-
+
parser.parse_bool('test')
-
+
def test_bool_parsing_empty(self):
+ ''' Test boolean parsing with empty string. '''
parser = CheddarOutputParser()
expected = None
result = parser.parse_bool('')
self.assertEquals(expected, result)
-
+
def test_int_parsing(self):
+ ''' Test integer parsing with integer as string. '''
parser = CheddarOutputParser()
-
+
expected = 234
result = parser.parse_int('234')
-
+
self.assertEquals(expected, result)
-
+
@raises(ParseError)
def test_int_parsing_error(self):
+ ''' Test integer parsing with non-integer string. '''
parser = CheddarOutputParser()
-
+
parser.parse_int('test')
-
+
def test_int_parsing_empty(self):
+ ''' Test integer parsing with empty string. '''
parser = CheddarOutputParser()
expected = None
result = parser.parse_int('')
self.assertEquals(expected, result)
-
+
def test_decimal_parsing(self):
+ ''' Test decimal parsing with decimal string. '''
parser = CheddarOutputParser()
-
+
expected = Decimal('2.345')
result = parser.parse_decimal('2.345')
-
+
self.assertEquals(expected, result)
-
+
@raises(ParseError)
def test_decimal_parsing_error(self):
+ ''' Test decimal parsing with non-decimal string. '''
parser = CheddarOutputParser()
-
+
parser.parse_decimal('test')
-
+
def test_decimal_parsing_empty(self):
+ ''' Test decimal parsing with empty string. '''
parser = CheddarOutputParser()
expected = None
result = parser.parse_decimal('')
self.assertEquals(expected, result)
-
+
def test_datetime_parsing(self):
+ ''' Test datetime parsing. '''
parser = CheddarOutputParser()
-
+
expected = datetime(
year=2011,
month=1,
@@ -106,27 +121,29 @@ def test_datetime_parsing(self):
tzinfo=tzutc(),
)
result = parser.parse_datetime('2011-01-07T20:46:43+00:00')
-
+
self.assertEquals(expected, result)
-
+
@raises(ParseError)
def test_datetime_parsing_error(self):
+ ''' Test datetime parsing with non-date string. '''
parser = CheddarOutputParser()
-
+
parser.parse_datetime('test')
-
+
def test_datetime_parsing_empty(self):
+ ''' Test datetime parsing with empty string. '''
parser = CheddarOutputParser()
-
+
expected = None
result = parser.parse_datetime('')
-
+
self.assertEquals(expected, result)
-
-
+
def test_error_parser(self):
+ ''' Test error parser. '''
error_xml = self.load_file('error.xml')
-
+
expected = {
'aux_code': '',
'code': '400',
@@ -134,19 +151,23 @@ def test_error_parser(self):
'message': 'No product selected. Need a productId or productCode.',
}
result = parse_error(error_xml)
-
+
self.assertEquals(expected, result)
-
+
def test_plans_parser(self):
+ ''' Test plans parser. '''
plans_xml = self.load_file('plans.xml')
parser = PlansParser()
-
- expected = [ { 'billing_frequency': 'monthly',
+
+ expected = [
+ {
+ 'billing_frequency': 'monthly',
'billing_frequency_per': 'month',
'billing_frequency_quantity': 1,
'billing_frequency_unit': 'months',
'code': 'FREE_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 20, 46, 43, tzinfo=tzutc()),
+ 'created_datetime': datetime(2011, 1, 7, 20, 46, 43,
+ tzinfo=tzutc()),
'description': 'A free monthly plan',
'id': '6b0d13f4-6bef-102e-b098-40402145ee8b',
'initial_bill_count': 1,
@@ -160,12 +181,14 @@ def test_plans_parser(self):
'setup_charge_amount': Decimal('0.00'),
'setup_charge_code': '',
'trial_days': 0},
- { 'billing_frequency': 'monthly',
+ {
+ 'billing_frequency': 'monthly',
'billing_frequency_per': 'month',
'billing_frequency_quantity': 1,
'billing_frequency_unit': 'months',
'code': 'PAID_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 21, 5, 42, tzinfo=tzutc()),
+ 'created_datetime': datetime(2011, 1, 7, 21, 5, 42,
+ tzinfo=tzutc()),
'description': '',
'id': '11af9cfc-6bf2-102e-b098-40402145ee8b',
'initial_bill_count': 1,
@@ -184,445 +207,565 @@ def test_plans_parser(self):
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
self.assertEquals(expected, result)
-
+
def test_plans_parser_with_items(self):
+ ''' Test plans parser with items. '''
plans_xml = self.load_file('plans_with_items.xml')
parser = PlansParser()
- expected = [ { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'FREE_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 20, 46, 43, tzinfo=tzutc()),
- 'description': 'A free monthly plan',
- 'id': '6b0d13f4-6bef-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': True,
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Monthly Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Once Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')}],
- 'name': 'Free Monthly',
- 'recurring_charge_amount': Decimal('0.00'),
- 'recurring_charge_code': 'FREE_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0},
- { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'TRACKED_MONTHLY',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'description': '',
- 'id': 'd19974a6-6e5a-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': False,
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': True,
- 'name': 'Monthly Item',
- 'overage_amount': Decimal('10.00'),
- 'quantity_included': Decimal('2')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Once Item',
- 'overage_amount': Decimal('10.00'),
- 'quantity_included': Decimal('0')}],
- 'name': 'Tracked Monthly',
- 'recurring_charge_amount': Decimal('10.00'),
- 'recurring_charge_code': 'TRACKED_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0},
- { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'PAID_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 21, 5, 42, tzinfo=tzutc()),
- 'description': '',
- 'id': '11af9cfc-6bf2-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': False,
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Monthly Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Once Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')}],
- 'name': 'Paid Monthly',
- 'recurring_charge_amount': Decimal('20.00'),
- 'recurring_charge_code': 'PAID_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0}]
+ expected = [
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'FREE_MONTHLY',
+ 'created_datetime': datetime(2011, 1, 7, 20, 46, 43,
+ tzinfo=tzutc()),
+ 'description': 'A free monthly plan',
+ 'id': '6b0d13f4-6bef-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': True,
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Monthly Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Once Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')}],
+ 'name': 'Free Monthly',
+ 'recurring_charge_amount': Decimal('0.00'),
+ 'recurring_charge_code': 'FREE_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0},
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'TRACKED_MONTHLY',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'id': 'd19974a6-6e5a-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': False,
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': True,
+ 'name': 'Monthly Item',
+ 'overage_amount': Decimal('10.00'),
+ 'quantity_included': Decimal('2')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Once Item',
+ 'overage_amount': Decimal('10.00'),
+ 'quantity_included': Decimal('0')}],
+ 'name': 'Tracked Monthly',
+ 'recurring_charge_amount': Decimal('10.00'),
+ 'recurring_charge_code': 'TRACKED_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0},
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'PAID_MONTHLY',
+ 'created_datetime': datetime(2011, 1, 7, 21, 5, 42,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'id': '11af9cfc-6bf2-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': False,
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Monthly Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Once Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')}],
+ 'name': 'Paid Monthly',
+ 'recurring_charge_amount': Decimal('20.00'),
+ 'recurring_charge_code': 'PAID_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0}]
result = parser.parse_xml(plans_xml)
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
self.assertEquals(expected, result)
-
+
def test_customers_parser_with_no_items(self):
+ ''' Test customers parser with no items. '''
customers_xml = self.load_file('customers-without-items.xml')
parser = CustomersParser()
-
- expected = [ { 'campaign_content': '',
- 'campaign_medium': '',
- 'campaign_name': '',
- 'campaign_source': '',
- 'campaign_term': '',
- 'code': 'test',
- 'company': '',
- 'created_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'email': 'garbage@saaspire.com',
- 'first_contact_datetime': None,
- 'first_name': 'Test',
- 'gateway_token': None,
- 'id': '10681b62-6dcd-102e-b098-40402145ee8b',
- 'is_vat_exempt': '0',
- 'last_name': 'User',
- 'meta_data': [ { 'created_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'id': '106953e2-6dcd-102e-b098-40402145ee8b',
- 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'name': 'key2',
- 'value': 'value_2'},
- { 'created_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'id': '1068b7a2-6dcd-102e-b098-40402145ee8b',
- 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'name': 'key_1',
- 'value': 'value_1'}],
- 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'notes': '',
- 'referer': '',
- 'referer_host': '',
- 'subscriptions': [ { 'cancel_reason': None,
- 'cancel_type': None,
- 'canceled_datetime': None,
- 'cc_address': '',
- 'cc_city': '',
- 'cc_company': '',
- 'cc_country': '',
- 'cc_email': None,
- 'cc_expiration_date': '',
- 'cc_first_name': '',
- 'cc_last_four': '',
- 'cc_last_name': '',
- 'cc_state': '',
- 'cc_type': '',
- 'cc_zip': '',
- 'created_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'gateway_token': '',
- 'id': '106953e3-6dcd-102e-b098-40402145ee8b',
- 'invoices': [ { 'billing_datetime': datetime(2011, 2, 10, 5, 45, 51, tzinfo=tzutc()),
- 'charges': [ { 'code': 'FREE_MONTHLY_RECURRING',
- 'created_datetime': datetime(2011, 2, 10, 5, 45, 51, tzinfo=tzutc()),
- 'description': '',
- 'each_amount': Decimal('0.00'),
- 'id': '',
- 'quantity': Decimal('1'),
- 'type': 'recurring'}],
- 'created_datetime': datetime(2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
- 'id': '106ed222-6dcd-102e-b098-40402145ee8b',
- 'number': '1',
- 'paid_transaction_id': '',
- 'type': 'subscription',
- 'vat_rate': ''}],
- 'items': [],
- 'plans': [ { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'FREE_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 20, 46, 43, tzinfo=tzutc()),
- 'description': 'A free monthly plan',
- 'id': '6b0d13f4-6bef-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': True,
- 'items': [],
- 'name': 'Free Monthly',
- 'recurring_charge_amount': Decimal('0.00'),
- 'recurring_charge_code': 'FREE_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0}],
- 'redirect_url': None}],
- 'vat_number': ''}]
+
+ expected = [
+ {
+ 'campaign_content': '',
+ 'campaign_medium': '',
+ 'campaign_name': '',
+ 'campaign_source': '',
+ 'campaign_term': '',
+ 'code': 'test',
+ 'company': '',
+ 'created_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'email': 'garbage@saaspire.com',
+ 'first_contact_datetime': None,
+ 'first_name': 'Test',
+ 'gateway_token': None,
+ 'id': '10681b62-6dcd-102e-b098-40402145ee8b',
+ 'is_vat_exempt': '0',
+ 'last_name': 'User',
+ 'meta_data': [
+ {
+ 'created_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'id': '106953e2-6dcd-102e-b098-40402145ee8b',
+ 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'name': 'key2',
+ 'value': 'value_2'},
+ {
+ 'created_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'id': '1068b7a2-6dcd-102e-b098-40402145ee8b',
+ 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'name': 'key_1',
+ 'value': 'value_1'}],
+ 'modified_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'notes': '',
+ 'referer': '',
+ 'referer_host': '',
+ 'subscriptions': [
+ {
+ 'cancel_reason': None,
+ 'cancel_type': None,
+ 'canceled_datetime': None,
+ 'cc_address': '',
+ 'coupon_code': None,
+ 'cc_city': '',
+ 'cc_company': '',
+ 'cc_country': '',
+ 'cc_email': None,
+ 'cc_expiration_date': '',
+ 'cc_first_name': '',
+ 'cc_last_four': '',
+ 'cc_last_name': '',
+ 'cc_state': '',
+ 'cc_type': '',
+ 'cc_zip': '',
+ 'created_datetime': datetime(2011, 1, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'gateway_token': '',
+ 'id': '106953e3-6dcd-102e-b098-40402145ee8b',
+ 'invoices': [
+ {
+ 'billing_datetime': datetime(
+ 2011, 2, 10, 5, 45, 51, tzinfo=tzutc()),
+ 'charges': [
+ {
+ 'code': 'FREE_MONTHLY_RECURRING',
+ 'created_datetime': datetime(
+ 2011, 2, 10, 5, 45, 51,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'each_amount': Decimal('0.00'),
+ 'id': '',
+ 'quantity': Decimal('1'),
+ 'type': 'recurring'}],
+ 'created_datetime': datetime(
+ 2011, 1, 10, 5, 45, 51, tzinfo=tzutc()),
+ 'id': '106ed222-6dcd-102e-b098-40402145ee8b',
+ 'number': '1',
+ 'paid_transaction_id': '',
+ 'type': 'subscription',
+ 'vat_rate': ''}],
+ 'items': [],
+ 'plans': [
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'FREE_MONTHLY',
+ 'created_datetime': datetime(
+ 2011, 1, 7, 20, 46, 43,
+ tzinfo=tzutc()),
+ 'description': 'A free monthly plan',
+ 'id': '6b0d13f4-6bef-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': True,
+ 'items': [],
+ 'name': 'Free Monthly',
+ 'recurring_charge_amount': Decimal('0.00'),
+ 'recurring_charge_code': 'FREE_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0}],
+ 'redirect_url': None}],
+ 'vat_number': ''}]
result = parser.parse_xml(customers_xml)
-
+
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
-
+
self.assertEquals(expected, result)
-
+
def test_customers_parser_with_items(self):
+ ''' Test customers parser with items. '''
customers_xml = self.load_file('customers-with-items.xml')
parser = CustomersParser()
-
- expected = [ { 'campaign_content': '',
- 'campaign_medium': '',
- 'campaign_name': '',
- 'campaign_source': '',
- 'campaign_term': '',
- 'code': 'test',
- 'company': '',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'email': 'garbage@saaspire.com',
- 'first_contact_datetime': None,
- 'first_name': 'Test',
- 'gateway_token': None,
- 'id': 'a1f143e0-6e65-102e-b098-40402145ee8b',
- 'is_vat_exempt': '0',
- 'last_name': 'User',
- 'meta_data': [],
- 'modified_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'notes': '',
- 'referer': '',
- 'referer_host': '',
- 'subscriptions': [ { 'cancel_reason': None,
- 'cancel_type': None,
- 'canceled_datetime': None,
- 'cc_address': '123 Something St',
- 'cc_city': 'Someplace',
- 'cc_company': 'Some Co LLC',
- 'cc_country': 'United States',
- 'cc_email': None,
- 'cc_expiration_date': '2011-07-31T00:00:00+00:00',
- 'cc_first_name': 'Test',
- 'cc_last_four': '1111',
- 'cc_last_name': 'User',
- 'cc_state': 'NY',
- 'cc_type': 'visa',
- 'cc_zip': '12345',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'gateway_token': 'SIMULATED',
- 'id': 'a1f27c60-6e65-102e-b098-40402145ee8b',
- 'invoices': [ { 'billing_datetime': datetime(2011, 2, 10, 23, 57, 58, tzinfo=tzutc()),
- 'charges': [ { 'code': 'TRACKED_MONTHLY_RECURRING',
- 'created_datetime': datetime(2011, 2, 10, 23, 57, 58, tzinfo=tzutc()),
- 'description': '',
- 'each_amount': Decimal('10.00'),
- 'id': '',
- 'quantity': Decimal('1'),
- 'type': 'recurring'},
- { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'description': '',
- 'each_amount': Decimal('10.00'),
- 'id': '',
- 'quantity': Decimal('1'),
- 'type': 'item'},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'description': '',
- 'each_amount': Decimal('10.00'),
- 'id': '',
- 'quantity': Decimal('1'),
- 'type': 'item'}],
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'id': 'a1f7faaa-6e65-102e-b098-40402145ee8b',
- 'number': '1',
- 'paid_transaction_id': '',
- 'type': 'subscription',
- 'vat_rate': ''}],
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'modified_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'name': 'Monthly Item',
- 'quantity': Decimal('3')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'modified_datetime': datetime(2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
- 'name': 'Once Item',
- 'quantity': Decimal('1')}],
- 'plans': [ { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'TRACKED_MONTHLY',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'description': '',
- 'id': 'd19974a6-6e5a-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': False,
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': True,
- 'name': 'Monthly Item',
- 'overage_amount': Decimal('10.00'),
- 'quantity_included': Decimal('2')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Once Item',
- 'overage_amount': Decimal('10.00'),
- 'quantity_included': Decimal('0')}],
- 'name': 'Tracked Monthly',
- 'recurring_charge_amount': Decimal('10.00'),
- 'recurring_charge_code': 'TRACKED_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0}],
- 'redirect_url': None}],
- 'vat_number': ''}]
+
+ expected = [
+ {
+ 'campaign_content': '',
+ 'campaign_medium': '',
+ 'campaign_name': '',
+ 'campaign_source': '',
+ 'campaign_term': '',
+ 'code': 'test',
+ 'company': '',
+ 'created_datetime': datetime(2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'email': 'garbage@saaspire.com',
+ 'first_contact_datetime': None,
+ 'first_name': 'Test',
+ 'gateway_token': None,
+ 'id': 'a1f143e0-6e65-102e-b098-40402145ee8b',
+ 'is_vat_exempt': '0',
+ 'last_name': 'User',
+ 'meta_data': [],
+ 'modified_datetime': datetime(2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'notes': '',
+ 'referer': '',
+ 'referer_host': '',
+ 'subscriptions': [
+ {
+ 'cancel_reason': None,
+ 'cancel_type': None,
+ 'canceled_datetime': None,
+ 'cc_address': '123 Something St',
+ 'coupon_code': None,
+ 'cc_city': 'Someplace',
+ 'cc_company': 'Some Co LLC',
+ 'cc_country': 'United States',
+ 'cc_email': None,
+ 'cc_expiration_date': '2011-07-31T00:00:00+00:00',
+ 'cc_first_name': 'Test',
+ 'cc_last_four': '1111',
+ 'cc_last_name': 'User',
+ 'cc_state': 'NY',
+ 'cc_type': 'visa',
+ 'cc_zip': '12345',
+ 'created_datetime': datetime(2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'gateway_token': 'SIMULATED',
+ 'id': 'a1f27c60-6e65-102e-b098-40402145ee8b',
+ 'invoices': [
+ {
+ 'billing_datetime': datetime(2011, 2, 10, 23,
+ 57, 58,
+ tzinfo=tzutc()),
+ 'charges': [
+ {
+ 'code': 'TRACKED_MONTHLY_RECURRING',
+ 'created_datetime': datetime(
+ 2011, 2, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'each_amount': Decimal('10.00'),
+ 'id': '',
+ 'quantity': Decimal('1'),
+ 'type': 'recurring'},
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'each_amount': Decimal('10.00'),
+ 'id': '',
+ 'quantity': Decimal('1'),
+ 'type': 'item'},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'each_amount': Decimal('10.00'),
+ 'id': '',
+ 'quantity': Decimal('1'),
+ 'type': 'item'}],
+ 'created_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58,
+ tzinfo=tzutc()),
+ 'id': 'a1f7faaa-6e65-102e-b098-40402145ee8b',
+ 'number': '1',
+ 'paid_transaction_id': '',
+ 'type': 'subscription',
+ 'vat_rate': ''}],
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'modified_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
+ 'name': 'Monthly Item',
+ 'quantity': Decimal('3')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'modified_datetime': datetime(
+ 2011, 1, 10, 23, 57, 58, tzinfo=tzutc()),
+ 'name': 'Once Item',
+ 'quantity': Decimal('1')}],
+ 'plans': [
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'TRACKED_MONTHLY',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
+ 'description': '',
+ 'id': 'd19974a6-6e5a-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': False,
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': True,
+ 'name': 'Monthly Item',
+ 'overage_amount': Decimal('10.00'),
+ 'quantity_included': Decimal('2')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Once Item',
+ 'overage_amount': Decimal('10.00'),
+ 'quantity_included': Decimal('0')}],
+ 'name': 'Tracked Monthly',
+ 'recurring_charge_amount': Decimal('10.00'),
+ 'recurring_charge_code': 'TRACKED_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0}],
+ 'redirect_url': None}],
+ 'vat_number': ''}]
result = parser.parse_xml(customers_xml)
-
+
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
self.assertEquals(expected, result)
- #import pprint
- #pp = pprint.PrettyPrinter(indent=4)
- #pp.pprint(result)
- #assert False
def test_paypal_customer_parse(self):
+ ''' Test customer parser with paypal customer. '''
customers_xml = self.load_file('paypal_customer.xml')
parser = CustomersParser()
-
- expected = [ { 'campaign_content': '',
- 'campaign_medium': '',
- 'campaign_name': '',
- 'campaign_source': '',
- 'campaign_term': '',
- 'code': 'test',
- 'company': '',
- 'created_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
- 'email': 'garbage@saaspire.com',
- 'first_contact_datetime': None,
- 'first_name': 'Test',
- 'gateway_token': None,
- 'id': '95d7696a-7fda-11e0-a51b-40403c39f8d9',
- 'is_vat_exempt': '0',
- 'last_name': 'User',
- 'meta_data': [],
- 'modified_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
- 'notes': '',
- 'referer': '',
- 'referer_host': '',
- 'subscriptions': [ { 'cancel_reason': 'PayPal preapproval is pending',
- 'cancel_type': 'paypal-wait',
- 'canceled_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
- 'cc_address': '',
- 'cc_city': '',
- 'cc_company': '',
- 'cc_country': '',
- 'cc_email': '',
- 'cc_expiration_date': '2012-05-16T00:00:00+00:00',
- 'cc_first_name': 'Test',
- 'cc_last_four': '',
- 'cc_last_name': 'User',
- 'cc_state': '',
- 'cc_type': '',
- 'cc_zip': '',
- 'created_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
- 'gateway_account': { 'gateway': 'PayPal_Simulator',
- 'id': '303f9a50-7fda-11e0-a51b-40403c39f8d9',
- 'type': 'paypal'},
- 'gateway_token': 'SIMULATED-4dd152718371a',
- 'id': '95d804ba-7fda-11e0-a51b-40403c39f8d9',
- 'invoices': [ { 'billing_datetime': datetime(2011, 6, 16, 16, 36, 1, tzinfo=tzutc()),
- 'charges': [ { 'code': 'PAID_MONTHLY_RECURRING',
- 'created_datetime': datetime(2011, 6, 16, 16, 36, 1, tzinfo=tzutc()),
- 'description': '',
- 'each_amount': Decimal('20.00'),
- 'id': '',
- 'quantity': Decimal('1'),
- 'type': 'recurring'}],
- 'created_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
- 'id': '95de499c-7fda-11e0-a51b-40403c39f8d9',
- 'number': '1',
- 'paid_transaction_id': '',
- 'type': 'subscription',
- 'vat_rate': ''}],
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': None,
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'modified_datetime': None,
- 'name': 'Monthly Item',
- 'quantity': Decimal('0')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': None,
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'modified_datetime': None,
- 'name': 'Once Item',
- 'quantity': Decimal('0')}],
- 'plans': [ { 'billing_frequency': 'monthly',
- 'billing_frequency_per': 'month',
- 'billing_frequency_quantity': 1,
- 'billing_frequency_unit': 'months',
- 'code': 'PAID_MONTHLY',
- 'created_datetime': datetime(2011, 1, 7, 21, 5, 42, tzinfo=tzutc()),
- 'description': '',
- 'id': '11af9cfc-6bf2-102e-b098-40402145ee8b',
- 'initial_bill_count': 1,
- 'initial_bill_count_unit': 'months',
- 'is_active': True,
- 'is_free': False,
- 'items': [ { 'code': 'MONTHLY_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Monthly Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')},
- { 'code': 'ONCE_ITEM',
- 'created_datetime': datetime(2011, 1, 10, 22, 40, 34, tzinfo=tzutc()),
- 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
- 'is_periodic': False,
- 'name': 'Once Item',
- 'overage_amount': Decimal('0.00'),
- 'quantity_included': Decimal('0')}],
- 'name': 'Paid Monthly',
- 'recurring_charge_amount': Decimal('20.00'),
- 'recurring_charge_code': 'PAID_MONTHLY_RECURRING',
- 'setup_charge_amount': Decimal('0.00'),
- 'setup_charge_code': '',
- 'trial_days': 0}],
- 'redirect_url': 'https://cheddargetter.com/service/paypal/simulate/productId/2ccbecd6-6beb-102e-b098-40402145ee8b/id/95d7696a-7fda-11e0-a51b-40403c39f8d9?preapprovalkey=SIMULATED-4dd152718371a'}],
- 'vat_number': ''}]
+
+ expected = [
+ {
+ 'campaign_content': '',
+ 'campaign_medium': '',
+ 'campaign_name': '',
+ 'campaign_source': '',
+ 'campaign_term': '',
+ 'code': 'test',
+ 'company': '',
+ 'created_datetime': datetime(2011, 5, 16, 16, 36, 1,
+ tzinfo=tzutc()),
+ 'email': 'garbage@saaspire.com',
+ 'first_contact_datetime': None,
+ 'first_name': 'Test',
+ 'gateway_token': None,
+ 'id': '95d7696a-7fda-11e0-a51b-40403c39f8d9',
+ 'is_vat_exempt': '0',
+ 'last_name': 'User',
+ 'meta_data': [],
+ 'modified_datetime': datetime(2011, 5, 16, 16, 36, 1,
+ tzinfo=tzutc()),
+ 'notes': '',
+ 'referer': '',
+ 'referer_host': '',
+ 'subscriptions': [
+ {
+ 'cancel_reason': 'PayPal preapproval is pending',
+ 'cancel_type': 'paypal-wait',
+ 'canceled_datetime': datetime(2011, 5, 16, 16, 36, 1,
+ tzinfo=tzutc()),
+ 'cc_address': '',
+ 'coupon_code': None,
+ 'cc_city': '',
+ 'cc_company': '',
+ 'cc_country': '',
+ 'cc_email': '',
+ 'cc_expiration_date': '2012-05-16T00:00:00+00:00',
+ 'cc_first_name': 'Test',
+ 'cc_last_four': '',
+ 'cc_last_name': 'User',
+ 'cc_state': '',
+ 'cc_type': '',
+ 'cc_zip': '',
+ 'created_datetime': datetime(2011, 5, 16, 16, 36, 1,
+ tzinfo=tzutc()),
+ 'gateway_account': {
+ 'gateway': 'PayPal_Simulator',
+ 'id': '303f9a50-7fda-11e0-a51b-40403c39f8d9',
+ 'type': 'paypal'},
+ 'gateway_token': 'SIMULATED-4dd152718371a',
+ 'id': '95d804ba-7fda-11e0-a51b-40403c39f8d9',
+ 'invoices': [
+ {
+ 'billing_datetime': datetime(
+ 2011, 6, 16, 16, 36, 1, tzinfo=tzutc()),
+ 'charges': [
+ {
+ 'code': 'PAID_MONTHLY_RECURRING',
+ 'created_datetime': datetime(
+ 2011, 6, 16, 16, 36, 1,
+ tzinfo=tzutc()),
+ 'description': '',
+ 'each_amount': Decimal('20.00'),
+ 'id': '',
+ 'quantity': Decimal('1'),
+ 'type': 'recurring'}],
+ 'created_datetime': datetime(
+ 2011, 5, 16, 16, 36, 1, tzinfo=tzutc()),
+ 'id': '95de499c-7fda-11e0-a51b-40403c39f8d9',
+ 'number': '1',
+ 'paid_transaction_id': '',
+ 'type': 'subscription',
+ 'vat_rate': ''}],
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': None,
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'modified_datetime': None,
+ 'name': 'Monthly Item',
+ 'quantity': Decimal('0')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': None,
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'modified_datetime': None,
+ 'name': 'Once Item',
+ 'quantity': Decimal('0')}],
+ 'plans': [
+ {
+ 'billing_frequency': 'monthly',
+ 'billing_frequency_per': 'month',
+ 'billing_frequency_quantity': 1,
+ 'billing_frequency_unit': 'months',
+ 'code': 'PAID_MONTHLY',
+ 'created_datetime': datetime(
+ 2011, 1, 7, 21, 5, 42, tzinfo=tzutc()),
+ 'description': '',
+ 'id': '11af9cfc-6bf2-102e-b098-40402145ee8b',
+ 'initial_bill_count': 1,
+ 'initial_bill_count_unit': 'months',
+ 'is_active': True,
+ 'is_free': False,
+ 'items': [
+ {
+ 'code': 'MONTHLY_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19b4970-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Monthly Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')},
+ {
+ 'code': 'ONCE_ITEM',
+ 'created_datetime': datetime(
+ 2011, 1, 10, 22, 40, 34,
+ tzinfo=tzutc()),
+ 'id': 'd19ef2f0-6e5a-102e-b098-40402145ee8b',
+ 'is_periodic': False,
+ 'name': 'Once Item',
+ 'overage_amount': Decimal('0.00'),
+ 'quantity_included': Decimal('0')}],
+ 'name': 'Paid Monthly',
+ 'recurring_charge_amount': Decimal('20.00'),
+ 'recurring_charge_code': 'PAID_MONTHLY_RECURRING',
+ 'setup_charge_amount': Decimal('0.00'),
+ 'setup_charge_code': '',
+ 'trial_days': 0}],
+ 'redirect_url': 'https://cheddargetter.com/service/paypal/simulate/productId/2ccbecd6-6beb-102e-b098-40402145ee8b/id/95d7696a-7fda-11e0-a51b-40403c39f8d9?preapprovalkey=SIMULATED-4dd152718371a'}],
+ 'vat_number': ''}]
result = parser.parse_xml(customers_xml)
-
+
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(result)
self.assertEquals(expected, result)
-
diff --git a/tests/product_tests.py b/tests/product_tests.py
index 16dcbc9..95d4f20 100644
--- a/tests/product_tests.py
+++ b/tests/product_tests.py
@@ -1,11 +1,13 @@
+import random
+import string
+
from copy import copy
-from datetime import date, datetime, timedelta
+from datetime import datetime, timedelta
from decimal import Decimal
import unittest
-from unittest.case import SkipTest
from dateutil.relativedelta import relativedelta
-from dateutil.tz import *
+from dateutil.tz import tzutc
from nose.tools import raises
from testconfig import config
@@ -14,26 +16,27 @@
from testing_tools.decorators import clear_users
+
class ProductTests(unittest.TestCase):
-
- client_defaults = {
+
+ client_defaults = {
'username': config['cheddar']['username'],
'password': config['cheddar']['password'],
'product_code': config['cheddar']['product_code'],
'endpoint': config['cheddar']['endpoint'],
}
-
+
customer_defaults = {
'code': 'test',
- 'email':'garbage@saaspire.com',
+ 'email': 'garbage@saaspire.com',
'first_name': 'Test',
'last_name': 'User',
'plan_code': 'FREE_MONTHLY',
}
- paypal_defaults = {
+ paypal_defaults = {
'code': 'test',
- 'email':'garbage@saaspire.com',
+ 'email': 'garbage@saaspire.com',
'first_name': 'Test',
'last_name': 'User',
'plan_code': 'PAID_MONTHLY',
@@ -44,9 +47,9 @@ class ProductTests(unittest.TestCase):
'return_url': 'http://example.com/return',
'cancel_url': 'http://example.com/cancel',
}
-
+
exipration = datetime.now() + timedelta(days=180)
-
+
paid_defaults = {
'cc_number': '4111111111111111',
'cc_expiration': exipration.strftime('%m/%Y'),
@@ -61,13 +64,15 @@ class ProductTests(unittest.TestCase):
'cc_zip': '12345',
'plan_code': 'PAID_MONTHLY',
}
-
+
def get_product(self):
+ ''' Helper method for getting product. '''
product = CheddarProduct(**self.client_defaults)
-
+
return product
def test_repr(self):
+ ''' Test Product __repr__ method. '''
product = self.get_product()
expected = u'CheddarProduct: %s' % product.product_code
result = product.__repr__()
@@ -75,16 +80,18 @@ def test_repr(self):
self.assertEquals(expected, result)
def test_instantiate_product(self):
+ ''' Test product key. '''
product = self.get_product()
-
+
for key, value in self.client_defaults.items():
self.assertEquals(value, getattr(product.client, key))
-
+
def test_get_all_plans(self):
+ ''' Test product get_all_plans method. '''
product = self.get_product()
-
+
plans = product.get_all_plans()
-
+
for plan in plans:
if plan.code == 'FREE_MONTHLY':
free_plan = plan
@@ -92,38 +99,49 @@ def test_get_all_plans(self):
paid_plan = plan
elif plan.code == 'TRACKED_MONTHLY':
tracked_plan = plan
-
+
self.assertEquals('FREE_MONTHLY', free_plan.code)
self.assertEquals('PAID_MONTHLY', paid_plan.code)
self.assertEquals('TRACKED_MONTHLY', tracked_plan.code)
-
+
def test_get_plan(self):
+ ''' Test product get_plan method with plan code. '''
product = self.get_product()
code = 'PAID_MONTHLY'
plan = product.get_plan(code)
-
+
self.assertEquals(code, plan.code)
-
+
def test_plan_initial_bill_date(self):
+ ''' Test plan with initial bill date. '''
product = self.get_product()
code = 'PAID_MONTHLY'
plan = product.get_plan(code)
-
+
expected = datetime.utcnow().date() + relativedelta(months=1)
result = plan.initial_bill_date
-
+
self.assertEquals(expected, result)
-
+
def get_customer(self, **kwargs):
+ ''' Test Product get_customer method with filtering. '''
customer_data = copy(self.customer_defaults)
+ # We need to make unique customers with the same data.
+ # Cheddar recomends we pass a garbage field.
+ # http://support.cheddargetter.com/discussions/problems/8342-duplicate-post
+ # http://support.cheddargetter.com/kb/api-8/error-handling#duplicate
+ random_string = ''.join(random.SystemRandom().choice(
+ string.ascii_uppercase + string.digits) for _ in range(5))
+ customer_data.update({'notes': random_string})
customer_data.update(kwargs)
product = self.get_product()
-
+
customer = product.create_customer(**customer_data)
-
+
return customer
-
+
def get_customer_with_items(self, **kwargs):
+ ''' Test Product get_customer method with items. '''
data = copy(self.paid_defaults)
if 'items' in kwargs.keys():
items = kwargs['items']
@@ -131,101 +149,129 @@ def get_customer_with_items(self, **kwargs):
items = []
items.append({'code': 'MONTHLY_ITEM', 'quantity': 3})
items.append({'code': 'ONCE_ITEM'})
-
+
data['items'] = items
data['plan_code'] = 'TRACKED_MONTHLY'
customer = self.get_customer(**data)
-
+
return customer
-
+
@clear_users
def test_simple_create_customer(self):
+ ''' Test Create Customer with only the get_customer helper. '''
self.get_customer()
-
+
@clear_users
def test_create_customer_with_company(self):
+ ''' Test Create Customer with company name. '''
self.get_customer(company='Test Co')
-
+
@clear_users
def test_create_customer_with_meta_data(self):
- self.get_customer(meta_data = {'key_1': 'value_1', 'key2': 'value_2'})
-
+ ''' Test Create Customer with meta data. '''
+ self.get_customer(meta_data={'key_1': 'value_1', 'key2': 'value_2'})
+
@clear_users
def test_create_customer_with_true_vat_exempt(self):
+ ''' Test Create Customer with vat exempt true. '''
self.get_customer(is_vat_exempt=True)
-
+
@clear_users
def test_create_customer_with_false_vat_exempt(self):
+ ''' Test Create Customer with vat exempt false. '''
self.get_customer(is_vat_exempt=False)
-
+
@clear_users
def test_create_customer_with_vat_number(self):
+ ''' Test Create Customer with vat number. '''
self.get_customer(vat_number=12345)
-
+
@clear_users
def test_create_customer_with_notes(self):
+ ''' Test Create Customer with notes. '''
self.get_customer(notes='This is a test note!')
-
+
@clear_users
def test_create_customer_with_first_contact_datetime(self):
+ ''' Test Create Customer with first contact datetime. '''
self.get_customer(first_contact_datetime=datetime.now())
-
+
@clear_users
def test_create_customer_with_referer(self):
+ ''' Test Create Customer with referer. '''
self.get_customer(referer='http://saaspire.com/test.html')
-
+
@clear_users
def test_create_customer_with_campaign_term(self):
+ ''' Test Create Customer with campaign term. '''
self.get_customer(campaign_term='testing')
-
+
@clear_users
def test_create_customer_with_campaign_name(self):
+ ''' Test Create Customer with campaign name. '''
self.get_customer(campaign_name='testing')
-
+
@clear_users
def test_create_customer_with_campaign_source(self):
+ ''' Test Create Customer with campaign source. '''
self.get_customer(campaign_source='testing')
-
+
@clear_users
def test_create_customer_with_campaign_content(self):
+ ''' Test Create Customer with campaign content. '''
self.get_customer(campaign_content='testing')
-
+
@clear_users
def test_create_customer_with_initial_bill_date(self):
+ ''' Test Create Customer with initial bill date. '''
initial_bill_date = datetime.utcnow() + timedelta(days=60)
customer = self.get_customer(initial_bill_date=initial_bill_date)
invoice = customer.subscription.invoices[0]
real_bill_date = invoice['billing_datetime']
-
- # Sometimes cheddar getter will push the bill date to the next day
+
+ # Sometimes cheddar getter will push the bill date to the next day
# if the request is made around UTC midnight
diff = initial_bill_date.date() - real_bill_date.date()
self.assertLessEqual(diff.days, 1)
-
+
@clear_users
def test_create_paid_customer(self):
+ ''' Test Create Customer with payment. '''
self.get_customer(**self.paid_defaults)
-
+
@clear_users
def test_create_paid_customer_with_charges(self):
+ ''' Test Create Customer with payment and additional charges. '''
data = copy(self.paid_defaults)
charges = []
- charges.append({'code': 'test_charge_1', 'each_amount':2})
+ charges.append({'code': 'test_charge_1', 'each_amount': 2})
charges.append({'code': 'charge2', 'quantity': 3, 'each_amount': 4})
data['charges'] = charges
self.get_customer(**data)
-
+
+ @clear_users
+ def test_create_paid_customer_with_coupon_code(self):
+ ''' Test Create Customer with payment and coupon codes. '''
+ data = copy(self.paid_defaults)
+ data.update({'coupon_code': 'COUPON'})
+ self.get_customer(**data)
+
@clear_users
def test_create_paid_customer_with_decimal_charges(self):
+ '''
+ Test Create Customer with payment and additional decimal charges.
+ '''
data = copy(self.paid_defaults)
charges = []
- charges.append({'code': 'test_charge_1', 'each_amount': Decimal('2.30')})
+ charges.append({'code': 'test_charge_1',
+ 'each_amount': Decimal('2.30')})
charges.append({'code': 'charge2', 'each_amount': Decimal('-4.5')})
data['charges'] = charges
self.get_customer(**data)
@clear_users
def test_create_paid_customer_with_items(self):
+ ''' Test Create Customer with payment and additional items. '''
data = copy(self.paid_defaults)
items = []
items.append({'code': 'MONTHLY_ITEM', 'quantity': 3})
@@ -234,9 +280,12 @@ def test_create_paid_customer_with_items(self):
data['plan_code'] = 'TRACKED_MONTHLY'
self.get_customer(**data)
-
@clear_users
def test_create_paid_customer_with_decimal_quantity_items(self):
+ '''
+ Test Create Customer with payment and additional decimal quantity
+ items.
+ '''
data = copy(self.paid_defaults)
items = []
items.append({'code': 'MONTHLY_ITEM', 'quantity': Decimal('1.23456')})
@@ -247,11 +296,13 @@ def test_create_paid_customer_with_decimal_quantity_items(self):
@clear_users
def test_create_paypal_customer(self):
+ ''' Test Create Customer with paypal. '''
data = copy(self.paypal_defaults)
self.get_customer(**data)
@clear_users
def test_update_paypal_customer(self):
+ ''' Test Update Customer with paypal. '''
data = copy(self.paypal_defaults)
customer = self.get_customer(**data)
customer.update(
@@ -260,294 +311,337 @@ def test_update_paypal_customer(self):
cancel_url='http://example.com/update-cancel/',
)
-
@clear_users
def test_customer_repr(self):
+ ''' Test Customer __repr__ method. '''
customer = self.get_customer()
expected = 'Customer: Test User (test)'
result = repr(customer)
self.assertEquals(expected, result)
-
+
@clear_users
def test_subscription_repr(self):
+ ''' Test Subscription __repr__ method. '''
customer = self.get_customer()
subscription = customer.subscription
-
+
expected = 'Subscription:'
result = repr(subscription)
-
+
self.assertIn(expected, result)
-
+
@clear_users
def test_pricing_plan_repr(self):
+ ''' Test PricingPlan __repr__ method. '''
customer = self.get_customer()
subscription = customer.subscription
plan = subscription.plan
-
+
expected = 'PricingPlan: Free Monthly (FREE_MONTHLY)'
result = repr(plan)
-
+
self.assertEquals(expected, result)
-
-
+
@clear_users
def test_item_repr(self):
+ ''' Test Item __repr__ method. '''
customer = self.get_customer_with_items()
subscription = customer.subscription
item = subscription.items['MONTHLY_ITEM']
-
+
expected = 'Item: MONTHLY_ITEM for test'
result = repr(item)
-
+
self.assertEquals(expected, result)
-
+
@clear_users
def test_get_customers(self):
- customer1 = self.get_customer()
+ ''' Create two customers, verify 2 returned. '''
+ self.get_customer()
customer2_data = copy(self.paid_defaults)
customer2_data.update({
'code': 'test2',
- 'email':'garbage+2@saaspire.com',
+ 'email': 'garbage+2@saaspire.com',
'first_name': 'Test2',
'last_name': 'User2',
})
- customer2 = self.get_customer(**customer2_data)
+ self.get_customer(**customer2_data)
product = self.get_product()
-
+
+ # This test fails intermitently. I'm assuming network race condition
+ # due to creating customers and fetching all customers so quickly.
+ import time
+ time.sleep(0.5)
fetched_customers = product.get_customers()
-
self.assertEquals(2, len(fetched_customers))
-
+
@clear_users
def test_get_customer(self):
+ ''' Test getting a customer by code.. '''
created_customer = self.get_customer()
product = self.get_product()
-
+
fetched_customer = product.get_customer(code=created_customer.code)
-
+
self.assertEquals(created_customer.code, fetched_customer.code)
- self.assertEquals(created_customer.first_name, fetched_customer.first_name)
- self.assertEquals(created_customer.last_name, fetched_customer.last_name)
+ self.assertEquals(created_customer.first_name,
+ fetched_customer.first_name)
+ self.assertEquals(created_customer.last_name,
+ fetched_customer.last_name)
self.assertEquals(created_customer.email, fetched_customer.email)
-
+
@clear_users
def test_simple_customer_update(self):
+ ''' Test Update Customer. '''
new_name = 'Different'
customer = self.get_customer()
product = self.get_product()
-
+
customer.update(first_name=new_name)
self.assertEquals(new_name, customer.first_name)
-
+
fetched_customer = product.get_customer(code=customer.code)
self.assertEquals(customer.first_name, fetched_customer.first_name)
-
+
@clear_users
@raises(NotFound)
def test_delete_customer(self):
+ ''' Create a Customer and delete that customer. '''
customer = self.get_customer()
product = self.get_product()
-
+
fetched_customer = product.get_customer(code=customer.code)
self.assertEquals(customer.first_name, fetched_customer.first_name)
-
+
customer.delete()
fetched_customer = product.get_customer(code=customer.code)
-
-
+
@clear_users
def test_delete_all_customers(self):
- customer_1 = self.get_customer()
- customer_2 = self.get_customer(code='test2')
+ '''
+ Create two customers, verify 2 returned,
+ delete and verify 0 customers.
+ '''
+ self.get_customer()
+ self.get_customer(code='test2')
product = self.get_product()
-
+
+ # This test fails intermitently. I'm assuming network race condition
+ # due to creating customers and fetching all customers so quickly.
+ import time
+ time.sleep(0.5)
fetched_customers = product.get_customers()
self.assertEquals(2, len(fetched_customers))
-
+
product.delete_all_customers()
-
+
fetched_customers = product.get_customers()
self.assertEquals(0, len(fetched_customers))
-
+
@clear_users
def test_cancel_subscription(self):
+ ''' Test cancel subscription. '''
customer = self.get_customer()
customer.subscription.cancel()
-
+
now = datetime.now(tzutc())
canceled_on = customer.subscription.canceled
diff = now - canceled_on
limit = timedelta(seconds=10)
self.assertLess(diff, limit)
-
+
def assert_increment(self, quantity=None):
+ ''' Helper method for asserting increment in other tests. '''
customer = self.get_customer_with_items()
product = self.get_product()
item = customer.subscription.items['MONTHLY_ITEM']
-
+
old_quantity = item.quantity_used
item.increment(quantity)
new_quantity = item.quantity_used
diff = new_quantity - old_quantity
expected = Decimal(quantity or 1)
self.assertAlmostEqual(expected, diff, places=2)
-
+
fetched_customer = product.get_customer(code=customer.code)
- fetched_item = customer.subscription.items[item.code]
+ fetched_item = fetched_customer.subscription.items[item.code]
self.assertEquals(item.quantity_used, fetched_item.quantity_used)
-
+
@clear_users
def test_simple_increment(self):
+ ''' Test item increment. '''
self.assert_increment()
-
+
@clear_users
def test_int_increment(self):
+ ''' Test item increment with integer. '''
self.assert_increment(1)
-
+
@clear_users
def test_float_increment(self):
+ ''' Test item increment with float. '''
self.assert_increment(1.234)
-
+
@clear_users
def test_decimal_increment(self):
+ ''' Test item increment with decimal. '''
self.assert_increment(Decimal('1.234'))
-
+
def assert_decrement(self, quantity=None):
+ ''' Helper method for asserting decrement in other tests. '''
customer = self.get_customer_with_items()
product = self.get_product()
item = customer.subscription.items['MONTHLY_ITEM']
-
+
old_quantity = item.quantity_used
item.decrement(quantity)
new_quantity = item.quantity_used
diff = old_quantity - new_quantity
expected = Decimal(quantity or 1)
self.assertAlmostEqual(expected, diff, places=2)
-
+
fetched_customer = product.get_customer(code=customer.code)
- fetched_item = customer.subscription.items[item.code]
+ fetched_item = fetched_customer.subscription.items[item.code]
self.assertEquals(item.quantity_used, fetched_item.quantity_used)
-
+
@clear_users
def test_simple_decrement(self):
+ ''' Test item decrement. '''
self.assert_decrement()
-
+
@clear_users
def test_int_decrement(self):
+ ''' Test item decrement with integer. '''
self.assert_decrement(1)
-
+
@clear_users
def test_float_decrement(self):
+ ''' Test item decrement with float. '''
self.assert_decrement(1.234)
-
+
@clear_users
def test_decimal_decrement(self):
+ ''' Test item decrement with decimal. '''
self.assert_decrement(Decimal('1.234'))
-
+
def assert_set(self, quantity):
+ '''
+ Helper method for asserting item quantity has been set as expected.
+ '''
customer = self.get_customer_with_items()
product = self.get_product()
item = customer.subscription.items['MONTHLY_ITEM']
-
- old_quantity = item.quantity_used
+
item.set(quantity)
new_quantity = item.quantity_used
expected = Decimal(quantity)
self.assertAlmostEqual(expected, new_quantity, places=2)
-
+
fetched_customer = product.get_customer(code=customer.code)
- fetched_item = customer.subscription.items[item.code]
+ fetched_item = fetched_customer.subscription.items[item.code]
self.assertEquals(item.quantity_used, fetched_item.quantity_used)
-
+
@clear_users
def test_int_set(self):
+ ''' Test item set with integer. '''
self.assert_set(1)
-
+
@clear_users
def test_float_set(self):
+ ''' Test item set with float. '''
self.assert_set(1.234)
-
+
@clear_users
def test_decimal_set(self):
+ ''' Test item set with decimal. '''
self.assert_set(Decimal('1.234'))
-
+
def assert_charged(self, code, each_amount, quantity=None,
- description=None):
+ description=None):
+ ''' Helper method for asserting custom charges as expected. '''
customer = self.get_customer(**self.paid_defaults)
product = self.get_product()
-
+
customer.charge(
code=code,
each_amount=each_amount,
quantity=quantity,
description=description,
)
-
+
if description is None:
description = ''
-
+
found_charge = None
for invoice in customer.subscription.invoices:
for charge in invoice['charges']:
if charge['code'] == code:
found_charge = charge
-
- self.assertAlmostEqual(Decimal(each_amount), found_charge['each_amount'], places=2)
+
+ self.assertAlmostEqual(Decimal(each_amount),
+ found_charge['each_amount'], places=2)
self.assertEqual(quantity, found_charge['quantity'])
self.assertEqual(description, found_charge['description'])
-
+
fetched_customer = product.get_customer(code=customer.code)
fetched_charge = None
for invoice in fetched_customer.subscription.invoices:
for charge in invoice['charges']:
if charge['code'] == code:
fetched_charge = charge
-
- self.assertAlmostEqual(Decimal(each_amount), fetched_charge['each_amount'], places=2)
+
+ self.assertAlmostEqual(Decimal(each_amount),
+ fetched_charge['each_amount'], places=2)
self.assertEqual(quantity, fetched_charge['quantity'])
self.assertEqual(description, fetched_charge['description'])
-
+
@clear_users
def test_add_charge(self):
+ ''' Test adding a custom charge to an invoice. '''
self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1)
-
- @clear_users
- def test_add_float_charge(self):
- self.assert_charged(code='TEST-CHARGE', each_amount=2.3, quantity=2)
-
+
@clear_users
def test_add_float_charge(self):
+ ''' Test adding a custom charge to an invoice with float. '''
self.assert_charged(code='TEST-CHARGE', each_amount=2.3, quantity=2)
-
+
@clear_users
def test_add_decimal_charge(self):
- self.assert_charged(code='TEST-CHARGE', each_amount=Decimal('2.3'), quantity=3)
-
+ ''' Test adding a custom charge to an invoice with decimal. '''
+ self.assert_charged(code='TEST-CHARGE', each_amount=Decimal('2.3'),
+ quantity=3)
+
@clear_users
def test_add_charge_with_descriptions(self):
- self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1, description="A test charge")
-
+ ''' Test adding a custom charge to an invoice with descriptions. '''
+ self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1,
+ description="A test charge")
+
@clear_users
def test_add_credit(self):
+ ''' Test adding a custom charge to an invoice as credit. '''
self.assert_charged(code='TEST-CHARGE', each_amount=-1, quantity=1)
- def assertCharge(self, customer, code, each_amount, quantity, description='', invoice_type=None):
+ def assertCharge(self, customer, code, each_amount, quantity,
+ description='', invoice_type=None):
+ ''' Helper method for asserting custom charge as expected. '''
found_charge = None
for invoice in customer.subscription.invoices:
if invoice_type is None or invoice['type'] == invoice_type:
for charge in invoice['charges']:
if charge['code'] == code:
found_charge = charge
-
- self.assertAlmostEqual(Decimal(each_amount), found_charge['each_amount'], places=2)
+
+ self.assertAlmostEqual(Decimal(each_amount),
+ found_charge['each_amount'], places=2)
self.assertEqual(quantity, found_charge['quantity'])
self.assertEqual(description, found_charge['description'])
-
def assertOneTimeInvoice(self, charges):
+ ''' Helper method for asserting one time invoice as expected. '''
customer = self.get_customer(**self.paid_defaults)
product = self.get_product()
@@ -556,59 +650,105 @@ def assertOneTimeInvoice(self, charges):
for charge in charges:
self.assertCharge(
customer,
- code = charge['code'],
- quantity = charge['quantity'],
- each_amount = charge['each_amount'],
- description = charge.get('description', ''),
- invoice_type = 'one-time',
+ code=charge['code'],
+ quantity=charge['quantity'],
+ each_amount=charge['each_amount'],
+ description=charge.get('description', ''),
+ invoice_type='one-time',
)
fetched_customer = product.get_customer(code=customer.code)
for charge in charges:
self.assertCharge(
fetched_customer,
- code = charge['code'],
- quantity = charge['quantity'],
- each_amount = charge['each_amount'],
- description = charge.get('description', ''),
- invoice_type = 'one-time',
+ code=charge['code'],
+ quantity=charge['quantity'],
+ each_amount=charge['each_amount'],
+ description=charge.get('description', ''),
+ invoice_type='one-time',
)
@clear_users
def test_add_simple_one_time_invoice(self):
- charges = [{
- 'code': 'immediate-test',
- 'quantity': 1,
- 'each_amount': Decimal(5.23)
- },]
+ ''' Test adding a one time invoice. '''
+ charges = [
+ {
+ 'code': 'immediate-test',
+ 'quantity': 1,
+ 'each_amount': Decimal(5.23)
+ }]
self.assertOneTimeInvoice(charges)
@clear_users
def test_add_one_time_invoice_with_description(self):
- charges = [{
- 'code': 'immediate-test',
- 'quantity': 1,
- 'each_amount': Decimal(5.23),
- 'description': 'This is a test charge'
- },]
+ ''' Test adding a one time invoice with description. '''
+ charges = [
+ {
+ 'code': 'immediate-test',
+ 'quantity': 1,
+ 'each_amount': Decimal(5.23),
+ 'description': 'This is a test charge'
+ }]
self.assertOneTimeInvoice(charges)
-
@clear_users
def test_add_one_time_invoice_with_multiple_charges(self):
- charges = [{
- 'code': 'immediate-test',
- 'quantity': 1,
- 'each_amount': Decimal(5.23),
- 'description': 'This is a test charge'
- },
- {
- 'code': 'immediate-test-2',
- 'quantity': 3,
- 'each_amount': 15,
- 'description': 'This is another test charge'
- },]
+ ''' Test adding a one time invoice with multiple charges. '''
+ charges = [
+ {
+ 'code': 'immediate-test',
+ 'quantity': 1,
+ 'each_amount': Decimal(5.23),
+ 'description': 'This is a test charge'
+ },
+ {
+ 'code': 'immediate-test-2',
+ 'quantity': 3,
+ 'each_amount': 15,
+ 'description': 'This is another test charge'
+ }]
self.assertOneTimeInvoice(charges)
+
+ def test_get_all_promotions(self):
+ ''' Test get all promotions. '''
+ product = self.get_product()
+ promotions = product.get_all_promotions()
+
+ self.assertEquals(2, len(promotions))
+ for promotion in promotions:
+ assert promotion.coupons[0].get('code') in ('COUPON', 'COUPON2')
+
+ def test_get_promotion(self):
+ ''' Test get a single promotion. '''
+ product = self.get_product()
+ promotion = product.get_promotion('COUPON')
+
+ self.assertEqual(unicode(promotion), 'Coupon (COUPON)')
+ self.assertEqual(promotion.name, 'Coupon')
+ self.assertEqual(promotion.coupons[0].get('code'), 'COUPON')
+ self.assertEqual(promotion.incentives[0].get('percentage'), '10')
+ self.assertEqual(promotion.incentives[0].get('expiration_datetime'),
+ None)
+
+ def test_promotion_repr(self):
+ ''' Test the internal __repr___ method of Promotion. '''
+ product = self.get_product()
+ promotion = product.get_promotion('COUPON')
+
+ expected = 'Promotion: Coupon (COUPON)'
+ result = repr(promotion)
+
+ self.assertEquals(expected, result)
+
+ def test_promotion_unicode(self):
+ ''' Test the internal __unicode___ method of Promotion. '''
+ product = self.get_product()
+ promotion = product.get_promotion('COUPON')
+
+ expected = 'Coupon (COUPON)'
+ result = unicode(promotion)
+
+ self.assertEquals(expected, result)
diff --git a/tests/readme.rst b/tests/readme.rst
new file mode 100644
index 0000000..325ac3d
--- /dev/null
+++ b/tests/readme.rst
@@ -0,0 +1,74 @@
+Requirements
+============
+
+Inside a virtualenv, run:
+
+.. code::
+
+ pip install -r dev-requirements.txt
+
+Installing elementtree for Unit Testing
+=======================================================
+When trying to install elementtree, pip may report that there is no such package. If this happens to you, you can work around by downloading and installing it manually.
+
+.. code::
+
+ wget http://effbot.org/media/downloads/elementtree-1.2.6-20050316.zip
+ unzip elementtree-1.2.6-20050316.zip
+ cd elementtree-1.2.6-20050316/
+ pip install .
+
+CheddarGetter Setup
+=============
+You will also need to setup the correct plans in cheddar. You may want to set up a product intended just for testing.
+
+
+
+The following tracked items are required for unit tests:
+
++--------------+--------------+
+| Name | Code |
++==============+==============+
+| Once Item | ONCE_ITEM |
++--------------+--------------+
+| Monthly Item | MONTHLY_ITEM |
++--------------+--------------+
+
+The following plan codes are required for unit tests:
+
++-----------------+-----------------+---------+-----------+--------------+
+| Plan Name | Code | Price | ONCE_ITEM | MONTHLY_ITEM |
++=================+=================+=========+===========+==============+
+| Free Monthly | FREE_MONTHLY | $0.00 | 1 | 10 |
++-----------------+-----------------+---------+-----------+--------------+
+| Paid Monthly | PAID_MONTHLY | $10.00 | 1 | 10 |
++-----------------+-----------------+---------+-----------+--------------+
+| Tracked Monthly | TRACKED_MONTHLY | $10.00 | 1 | 10 |
++-----------------+-----------------+---------+-----------+--------------+
+
+
+The following promotions are required for unit tests:
+
++----------------+---------------+--------+-----------+
+| Promotion Name | Coupon Code | % Off | Duration |
++================+===============+========+===========+
+| Coupon | COUPON | 10 | Forever |
++----------------+---------------+--------+-----------+
+| Coupon 2 | COUPON2 | 20 | Forever |
++----------------+---------------+--------+-----------+
+
+Be sure to turn on the native gateway credit card option in Configuration > Product settings > Gateway Settings.
+Be sure to turn on the paypal option in Configuration > Product settings > Gateway Settings or Quick Setup > Billing solution. I checked the "Use standard payments (PayPal account to PayPal account)" checkbox.
+
+Config
+======
+
+In the tests folder, copy the config.ini.template to config.ini. Fill in your email, password, and product code.
+
+Running Tests
+=============
+Run the test with nosetests.
+
+.. code::
+
+ nosetests
diff --git a/tests/testing_tools/decorators.py b/tests/testing_tools/decorators.py
index c5fe985..39dc774 100644
--- a/tests/testing_tools/decorators.py
+++ b/tests/testing_tools/decorators.py
@@ -2,20 +2,20 @@
from utils import clear_users as clear_users_func
+
def clear_users(func):
'''
Calls cheddar's delete all users method no matter the test result
'''
def new(*args, **kwargs):
- raised_exception = None
try:
func(*args, **kwargs)
- except Exception, e:
+ except Exception:
clear_users_func()
raise
-
+
clear_users_func()
-
+
new = make_decorator(func)(new)
-
- return new
\ No newline at end of file
+
+ return new
diff --git a/tests/testing_tools/utils.py b/tests/testing_tools/utils.py
index faa96bf..68cb0c8 100644
--- a/tests/testing_tools/utils.py
+++ b/tests/testing_tools/utils.py
@@ -3,6 +3,7 @@
from testconfig import config
+
def clear_users():
username = config['cheddar']['username']
password = config['cheddar']['password']
@@ -12,9 +13,13 @@ def clear_users():
h = httplib2.Http()
h.add_credentials(username, password)
- url = '%s/customers/delete-all/confirm/%d/productCode/%s' % (endpoint, int(time()), product)
+ url = '%s/customers/delete-all/confirm/%d/productCode/%s' % (endpoint,
+ int(time()),
+ product)
response, content = h.request(url, 'POST')
- if response.status != 200:
- raise Exception('Could not clear users. Recieved a response of %s %s ' % (response.status, response.reason))
+ if response.status != 200 or 'success' not in content:
+ raise Exception(
+ 'Could not clear users. Recieved a response of %s %s \n %s' % (
+ response.status, response.reason, content))