From 0d92719bd8fd951031f26c7819a9eab233cbca73 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 16:02:25 -0400 Subject: [PATCH 01/28] Version Bump --- sharpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 92d66203e33ed20e5c675ef1574f29f39802025e Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 16:03:46 -0400 Subject: [PATCH 02/28] Add promotion parsers --- sharpy/parsers.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/sharpy/parsers.py b/sharpy/parsers.py index 6d4b417..8235e93 100644 --- a/sharpy/parsers.py +++ b/sharpy/parsers.py @@ -328,4 +328,79 @@ def parse_subscription_item(self, item_element): 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_promotions(self, promotions_element): + promotions = [] + if promotions_element is not None: + for promotion_element in promotions_element: + promotions.append(self.parse_promotion(promotion_element)) + + return promotions + + def parse_promotion(self, promotion_element): + promotion = {} + import pdb; pdb.set_trace() + 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 From 7f627b2cd905cb44938e9aaf7327daf5f60b3f18 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 16:04:01 -0400 Subject: [PATCH 03/28] Add promotion object to product. --- sharpy/product.py | 77 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/sharpy/product.py b/sharpy/product.py index cd6c0bc..17992b9 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -7,7 +7,7 @@ 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): @@ -263,8 +263,42 @@ def delete_all_customers(self): 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 + + response = self.client.make_request(path='promotions/get') + 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, @@ -576,12 +610,12 @@ def __init__(self, id, gateway_token, cc_first_name, cc_last_name, 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, redirect_url=None): self.id = id self.gateway_token = gateway_token @@ -764,5 +798,26 @@ def set(self, quantity): ) 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 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 \ No newline at end of file From 8b589acf3348a94af9d56bc4e837b518fa8d3243 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 16:10:41 -0400 Subject: [PATCH 04/28] Update readme with elementtree information --- README.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 630b8bb..25f40b2 100644 --- a/README.rst +++ b/README.rst @@ -50,8 +50,17 @@ 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. + + 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. From 72f6aa8d6bf050cd0b661c382de4114c9593691e Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 16:13:06 -0400 Subject: [PATCH 05/28] Formatting updates for code in readme --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 25f40b2..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 @@ -54,6 +56,8 @@ 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/ From 02b729539b2375998d411adfc9753401d455de71 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 17:28:38 -0400 Subject: [PATCH 06/28] Add readme for testing --- tests/readme.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/readme.rst diff --git a/tests/readme.rst b/tests/readme.rst new file mode 100644 index 0000000..6b41c7e --- /dev/null +++ b/tests/readme.rst @@ -0,0 +1,31 @@ +Requirements +============ + +Inside a virtualenv, run: + +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 . + +Config +====== + +In the tests folder, copy the config.ini.template to config.ini. Fill in your email, password, and product code. + +You will also need to setup the correct plans in cheddar. You may want to set up a product intended just for testing. + +Running Tests +============= +Run the test with nosetests. + +.. code:: + nosetests From ded5aed84cd42c9728bc7ac0c281ab34714208b0 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 17:29:37 -0400 Subject: [PATCH 07/28] Needs line break for code to show --- tests/readme.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/readme.rst b/tests/readme.rst index 6b41c7e..48e1dfc 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -28,4 +28,5 @@ Running Tests Run the test with nosetests. .. code:: + nosetests From acfc60cda7601530fed205e7fcb11b496b7de357 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 21 Aug 2015 17:30:10 -0400 Subject: [PATCH 08/28] Add code formatting to install line. --- tests/readme.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/readme.rst b/tests/readme.rst index 48e1dfc..241072c 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -3,7 +3,9 @@ Requirements Inside a virtualenv, run: -pip install -r dev-requirements.txt +.. code:: + + pip install -r dev-requirements.txt Installing elementtree for Unit Testing ======================================================= From 7ffa8a2be17463a4b810c1bf3c799082d02d73a5 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 11:11:47 -0400 Subject: [PATCH 09/28] Update readme with cheddargetter unittest setup. --- tests/readme.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/readme.rst b/tests/readme.rst index 241072c..6947dc4 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -23,8 +23,18 @@ Config In the tests folder, copy the config.ini.template to config.ini. Fill in your email, password, and product code. + +Cheddar 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 plan codes are required for unit tests: + +* FREE_MONTHLY +* PAID_MONTHLY + +Be sure you turn on the native gateway credit card option. + Running Tests ============= Run the test with nosetests. From 018c6daa6e19eb902c0e5affbe06bf104c03e6a1 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 11:12:39 -0400 Subject: [PATCH 10/28] CheddarGetter setup will probably happen before config. --- tests/readme.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/readme.rst b/tests/readme.rst index 6947dc4..4accffd 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -18,13 +18,7 @@ When trying to install elementtree, pip may report that there is no such package cd elementtree-1.2.6-20050316/ pip install . -Config -====== - -In the tests folder, copy the config.ini.template to config.ini. Fill in your email, password, and product code. - - -Cheddar Setup +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. @@ -35,6 +29,11 @@ The following plan codes are required for unit tests: Be sure you turn on the native gateway credit card option. +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. From 35fa3cc21be40a712b491a15ea9a291f07d5bb35 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 13:15:12 -0400 Subject: [PATCH 11/28] Update readme.rst --- tests/readme.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/readme.rst b/tests/readme.rst index 4accffd..a126579 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -26,6 +26,7 @@ The following plan codes are required for unit tests: * FREE_MONTHLY * PAID_MONTHLY +* TRACKED_MONTHLY Be sure you turn on the native gateway credit card option. From 6926b4f9dd7ce032661e7582854e5ef0829fd8bd Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 13:18:27 -0400 Subject: [PATCH 12/28] Update readme.rst --- tests/readme.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/readme.rst b/tests/readme.rst index a126579..08e2a8a 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -28,6 +28,11 @@ The following plan codes are required for unit tests: * PAID_MONTHLY * TRACKED_MONTHLY +The following tracked items are required for unit tests: + +* MONTHLY_ITEM +* ONCE_ITEM + Be sure you turn on the native gateway credit card option. Config From 4d0fd823206337b98bf1d32bfeb313fcaff532e7 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 13:43:45 -0400 Subject: [PATCH 13/28] Try to be more clean on plans. Update the plans to a table to include the names and number of tracked items. Some Unit tests are checking against these. --- tests/readme.rst | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/readme.rst b/tests/readme.rst index 08e2a8a..d865c99 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -22,18 +22,31 @@ 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 plan codes are required for unit tests: -* FREE_MONTHLY -* PAID_MONTHLY -* TRACKED_MONTHLY The following tracked items are required for unit tests: -* MONTHLY_ITEM -* ONCE_ITEM ++--------------+--------------+ +| Name | Code | ++==============+==============+ +| Once Item | ONCE_ITEM | ++--------------+--------------+ +| Monthly Item | MONTHLY_ITEM | ++--------------+--------------+ + +The following plan codes are required for unit tests: -Be sure you turn on the native gateway credit card option. ++-----------------+-----------------+---------+-----------+--------------+ +| 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 | ++-----------------+-----------------+---------+-----------+--------------+ + +Be sure to turn on the native gateway credit card option in Configuration > Product settings > Gateway Settings. Config ====== From fbed197cc3e06d64c9bc24753389ffd608f58aaa Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 14:31:42 -0400 Subject: [PATCH 14/28] Update readme.rst --- tests/readme.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/readme.rst b/tests/readme.rst index d865c99..4d03d60 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -46,6 +46,17 @@ The following plan codes are required for unit tests: | Tracked Monthly | TRACKED_MONTHLY | $10.00 | 1 | 10 | +-----------------+-----------------+---------+-----------+--------------+ + +The following promotions are required for unit tests: + ++----------------+---------------+--------+ +| Promotion Name | Coupon Code | % Off | ++================+===============+========+ +| Coupon | COUPON | 10 | ++----------------+---------------+--------+ +| Coupon 2 | COUPON2 | 20 | ++----------------+---------------+--------+ + Be sure to turn on the native gateway credit card option in Configuration > Product settings > Gateway Settings. Config From ed573e111589bb398ae1b0b610fcdf06cdaf55ba Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 14:32:46 -0400 Subject: [PATCH 15/28] Update readme.rst --- tests/readme.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/readme.rst b/tests/readme.rst index 4d03d60..59bb58e 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -49,13 +49,13 @@ The following plan codes are required for unit tests: The following promotions are required for unit tests: -+----------------+---------------+--------+ -| Promotion Name | Coupon Code | % Off | -+================+===============+========+ -| Coupon | COUPON | 10 | -+----------------+---------------+--------+ -| Coupon 2 | COUPON2 | 20 | -+----------------+---------------+--------+ ++----------------+---------------+--------+-----------+ +| 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. From f8f0500d116d6597660988d8bb9efd8857c079e1 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 15:17:34 -0400 Subject: [PATCH 16/28] Remove the debug code. --- sharpy/parsers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sharpy/parsers.py b/sharpy/parsers.py index 8235e93..7183c1f 100644 --- a/sharpy/parsers.py +++ b/sharpy/parsers.py @@ -354,7 +354,6 @@ def parse_promotions(self, promotions_element): def parse_promotion(self, promotion_element): promotion = {} - import pdb; pdb.set_trace() promotion['id'] = promotion_element.attrib['id'] promotion['name'] = promotion_element.findtext('name') promotion['description'] = promotion_element.findtext('description') From 12d08b322cd163a3b14a7d3e88fd0866b3705583 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 15:18:47 -0400 Subject: [PATCH 17/28] Don't request response twice. Verify response is return before trying to parse. Bubble up coupon code to promotion.code. --- sharpy/product.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sharpy/product.py b/sharpy/product.py index 17992b9..f7810b9 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -276,10 +276,10 @@ def get_all_promotions(self): except NotFound: response = None - response = self.client.make_request(path='promotions/get') - promotions_parser = PromotionsParser() - promotions_data = promotions_parser.parse_xml(response.content) - promotions = [Promotion(**promotion_data) for promotion_data in promotions_data] + 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 @@ -810,6 +810,9 @@ def __init__(self, id=None, code=None, name=None, description=None, super(Promotion, self).__init__() + 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): @@ -820,4 +823,8 @@ def load_data(self, id=None, code=None, name=None, description=None, self.created = created_datetime self.incentives = incentives - self.coupons = coupons \ No newline at end of file + 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') From 6072a77cc4d19fa0855f8f23ee320894bd79b39f Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Mon, 24 Aug 2015 15:20:20 -0400 Subject: [PATCH 18/28] Add unit test for new methods. Skipping failing tests due to issues on cheddar's side. --- tests/client_tests.py | 13 +++- tests/product_tests.py | 161 ++++++++++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 54 deletions(-) diff --git a/tests/client_tests.py b/tests/client_tests.py index 571cc64..10432ba 100644 --- a/tests/client_tests.py +++ b/tests/client_tests.py @@ -93,8 +93,9 @@ def test_make_request_access_denied(self): 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) @@ -104,7 +105,8 @@ def test_make_request_not_found(self): path = 'things-which-dont-exist' client = self.get_client() client.make_request(path) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_post_request(self): path = 'customers/new' @@ -118,7 +120,7 @@ def test_post_request(self): client = self.get_client() client.make_request(path, data=data) - + @unittest.skip('Skip until deleting customers is working') def generate_error_response(self, auxcode=None, path=None, params=None, **overrides): ''' Creates a request to cheddar which should return an error @@ -242,9 +244,12 @@ def test_format_datetime_with_now(self): self.assertEquals(expected, result) + @unittest.skip('I can not figure this out.') @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 } diff --git a/tests/product_tests.py b/tests/product_tests.py index 16dcbc9..c0852e5 100644 --- a/tests/product_tests.py +++ b/tests/product_tests.py @@ -137,59 +137,73 @@ def get_customer_with_items(self, **kwargs): customer = self.get_customer(**data) return customer - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_create_customer(self): self.get_customer() - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_company(self): self.get_customer(company='Test Co') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_meta_data(self): self.get_customer(meta_data = {'key_1': 'value_1', 'key2': 'value_2'}) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_true_vat_exempt(self): self.get_customer(is_vat_exempt=True) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_false_vat_exempt(self): self.get_customer(is_vat_exempt=False) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_vat_number(self): self.get_customer(vat_number=12345) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_notes(self): self.get_customer(notes='This is a test note!') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_first_contact_datetime(self): self.get_customer(first_contact_datetime=datetime.now()) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_referer(self): self.get_customer(referer='http://saaspire.com/test.html') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_term(self): self.get_customer(campaign_term='testing') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_name(self): self.get_customer(campaign_name='testing') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_source(self): self.get_customer(campaign_source='testing') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_content(self): self.get_customer(campaign_content='testing') - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_initial_bill_date(self): initial_bill_date = datetime.utcnow() + timedelta(days=60) @@ -201,11 +215,13 @@ def test_create_customer_with_initial_bill_date(self): # if the request is made around UTC midnight diff = initial_bill_date.date() - real_bill_date.date() self.assertLessEqual(diff.days, 1) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer(self): self.get_customer(**self.paid_defaults) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_charges(self): data = copy(self.paid_defaults) @@ -214,7 +230,8 @@ def test_create_paid_customer_with_charges(self): charges.append({'code': 'charge2', 'quantity': 3, 'each_amount': 4}) data['charges'] = charges self.get_customer(**data) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_decimal_charges(self): data = copy(self.paid_defaults) @@ -224,6 +241,7 @@ def test_create_paid_customer_with_decimal_charges(self): data['charges'] = charges self.get_customer(**data) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_items(self): data = copy(self.paid_defaults) @@ -234,7 +252,7 @@ def test_create_paid_customer_with_items(self): data['plan_code'] = 'TRACKED_MONTHLY' self.get_customer(**data) - + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_decimal_quantity_items(self): data = copy(self.paid_defaults) @@ -245,11 +263,13 @@ def test_create_paid_customer_with_decimal_quantity_items(self): data['plan_code'] = 'TRACKED_MONTHLY' self.get_customer(**data) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paypal_customer(self): data = copy(self.paypal_defaults) self.get_customer(**data) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_update_paypal_customer(self): data = copy(self.paypal_defaults) @@ -260,7 +280,7 @@ def test_update_paypal_customer(self): cancel_url='http://example.com/update-cancel/', ) - + @unittest.skip('Skip until deleting customers is working') @clear_users def test_customer_repr(self): customer = self.get_customer() @@ -269,7 +289,8 @@ def test_customer_repr(self): result = repr(customer) self.assertEquals(expected, result) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_subscription_repr(self): customer = self.get_customer() @@ -279,7 +300,8 @@ def test_subscription_repr(self): result = repr(subscription) self.assertIn(expected, result) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_pricing_plan_repr(self): customer = self.get_customer() @@ -290,8 +312,8 @@ def test_pricing_plan_repr(self): result = repr(plan) self.assertEquals(expected, result) - - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_item_repr(self): customer = self.get_customer_with_items() @@ -302,7 +324,8 @@ def test_item_repr(self): result = repr(item) self.assertEquals(expected, result) - + + @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_get_customers(self): customer1 = self.get_customer() @@ -319,7 +342,8 @@ def test_get_customers(self): fetched_customers = product.get_customers() self.assertEquals(2, len(fetched_customers)) - + + @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_get_customer(self): created_customer = self.get_customer() @@ -331,7 +355,8 @@ def test_get_customer(self): 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) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_customer_update(self): new_name = 'Different' @@ -343,7 +368,8 @@ def test_simple_customer_update(self): fetched_customer = product.get_customer(code=customer.code) self.assertEquals(customer.first_name, fetched_customer.first_name) - + + @unittest.skip('Skip until deleting customers is working') @clear_users @raises(NotFound) def test_delete_customer(self): @@ -355,8 +381,8 @@ def test_delete_customer(self): customer.delete() fetched_customer = product.get_customer(code=customer.code) - - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_delete_all_customers(self): customer_1 = self.get_customer() @@ -370,7 +396,8 @@ def test_delete_all_customers(self): fetched_customers = product.get_customers() self.assertEquals(0, len(fetched_customers)) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_cancel_subscription(self): customer = self.get_customer() @@ -398,19 +425,23 @@ def assert_increment(self, quantity=None): fetched_customer = product.get_customer(code=customer.code) fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_increment(self): self.assert_increment() - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_int_increment(self): self.assert_increment(1) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_increment(self): self.assert_increment(1.234) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_increment(self): self.assert_increment(Decimal('1.234')) @@ -430,19 +461,23 @@ def assert_decrement(self, quantity=None): fetched_customer = product.get_customer(code=customer.code) fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_decrement(self): self.assert_decrement() - + + @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_int_decrement(self): self.assert_decrement(1) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_decrement(self): self.assert_decrement(1.234) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_decrement(self): self.assert_decrement(Decimal('1.234')) @@ -461,15 +496,18 @@ def assert_set(self, quantity): fetched_customer = product.get_customer(code=customer.code) fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - + + @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_int_set(self): self.assert_set(1) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_set(self): self.assert_set(1.234) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_set(self): self.assert_set(Decimal('1.234')) @@ -509,27 +547,28 @@ def assert_charged(self, code, each_amount, quantity=None, self.assertAlmostEqual(Decimal(each_amount), fetched_charge['each_amount'], places=2) self.assertEqual(quantity, fetched_charge['quantity']) self.assertEqual(description, fetched_charge['description']) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_charge(self): 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) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_float_charge(self): self.assert_charged(code='TEST-CHARGE', each_amount=2.3, quantity=2) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_decimal_charge(self): self.assert_charged(code='TEST-CHARGE', each_amount=Decimal('2.3'), quantity=3) - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_charge_with_descriptions(self): self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1, description="A test charge") - + + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_credit(self): self.assert_charged(code='TEST-CHARGE', each_amount=-1, quantity=1) @@ -574,6 +613,7 @@ def assertOneTimeInvoice(self, charges): invoice_type = 'one-time', ) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_simple_one_time_invoice(self): charges = [{ @@ -584,6 +624,7 @@ def test_add_simple_one_time_invoice(self): self.assertOneTimeInvoice(charges) + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_one_time_invoice_with_description(self): charges = [{ @@ -595,7 +636,7 @@ def test_add_one_time_invoice_with_description(self): self.assertOneTimeInvoice(charges) - + @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_one_time_invoice_with_multiple_charges(self): charges = [{ @@ -612,3 +653,23 @@ def test_add_one_time_invoice_with_multiple_charges(self): },] 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) From 3a25cdf50c2c465fd1fd037426ffb3166c8836fd Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 13:33:26 -0400 Subject: [PATCH 19/28] Update readme.rst --- tests/readme.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/readme.rst b/tests/readme.rst index 59bb58e..325ac3d 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -58,6 +58,7 @@ The following promotions are required for unit tests: +----------------+---------------+--------+-----------+ 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 ====== From 1824bcfa7585a1ebc72482ef0f0e66d9147aad54 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 15:11:56 -0400 Subject: [PATCH 20/28] parse_promotions is not needed because it is not used. --- sharpy/parsers.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/sharpy/parsers.py b/sharpy/parsers.py index 7183c1f..52c7aa4 100644 --- a/sharpy/parsers.py +++ b/sharpy/parsers.py @@ -340,16 +340,7 @@ def parse_xml(self, xml_str): for promotion_xml in promotions_xml: promotion = self.parse_promotion(promotion_xml) promotions.append(promotion) - - return promotions - def parse_promotions(self, promotions_element): - promotions = [] - - if promotions_element is not None: - for promotion_element in promotions_element: - promotions.append(self.parse_promotion(promotion_element)) - return promotions def parse_promotion(self, promotion_element): From 84088308414b5da7024ff4a883860d1832e5a206 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 15:12:45 -0400 Subject: [PATCH 21/28] The convention used in the other classes is to include a __repr__. Include a __repr__ for Promotion. --- sharpy/product.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sharpy/product.py b/sharpy/product.py index f7810b9..817260d 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -810,6 +810,9 @@ def __init__(self, id=None, code=None, name=None, description=None, 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) From 721cd389db642be17e4b8f4c4d6b4945cbadb7c2 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 15:14:56 -0400 Subject: [PATCH 22/28] Also consider clearing customers a failure if success is not in the results. Related to a specific cheddar bug where it only returns . --- tests/testing_tools/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testing_tools/utils.py b/tests/testing_tools/utils.py index faa96bf..0b5d616 100644 --- a/tests/testing_tools/utils.py +++ b/tests/testing_tools/utils.py @@ -16,5 +16,5 @@ def clear_users(): 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)) From 6e22417789a4e4b25477f6fe0bb777bd9abd43f4 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 15:16:39 -0400 Subject: [PATCH 23/28] Remove skipped decorator from tests. --- tests/client_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/client_tests.py b/tests/client_tests.py index 10432ba..f3e612f 100644 --- a/tests/client_tests.py +++ b/tests/client_tests.py @@ -106,7 +106,6 @@ def test_make_request_not_found(self): client = self.get_client() client.make_request(path) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_post_request(self): path = 'customers/new' @@ -120,7 +119,6 @@ def test_post_request(self): client = self.get_client() client.make_request(path, data=data) - @unittest.skip('Skip until deleting customers is working') def generate_error_response(self, auxcode=None, path=None, params=None, **overrides): ''' Creates a request to cheddar which should return an error @@ -244,7 +242,6 @@ def test_format_datetime_with_now(self): self.assertEquals(expected, result) - @unittest.skip('I can not figure this out.') @clear_users def test_chedder_update_customer_error(self): """ From 77af8cc6b32313375de32b8b4248646b2332a021 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 15:18:04 -0400 Subject: [PATCH 24/28] Add unittests for __repr__ and __unicode__ on the Promotion object. Remove the skip test decorator from unit tests. Add work around for duplicate transaction failures. --- tests/product_tests.py | 84 ++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/tests/product_tests.py b/tests/product_tests.py index c0852e5..b834c8d 100644 --- a/tests/product_tests.py +++ b/tests/product_tests.py @@ -1,3 +1,6 @@ +import random +import string + from copy import copy from datetime import date, datetime, timedelta from decimal import Decimal @@ -116,6 +119,13 @@ def test_plan_initial_bill_date(self): def get_customer(self, **kwargs): 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() @@ -138,72 +148,58 @@ def get_customer_with_items(self, **kwargs): return customer - @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_create_customer(self): self.get_customer() - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_company(self): self.get_customer(company='Test Co') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_meta_data(self): self.get_customer(meta_data = {'key_1': 'value_1', 'key2': 'value_2'}) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_true_vat_exempt(self): self.get_customer(is_vat_exempt=True) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_false_vat_exempt(self): self.get_customer(is_vat_exempt=False) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_vat_number(self): self.get_customer(vat_number=12345) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_notes(self): self.get_customer(notes='This is a test note!') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_first_contact_datetime(self): self.get_customer(first_contact_datetime=datetime.now()) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_referer(self): self.get_customer(referer='http://saaspire.com/test.html') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_term(self): self.get_customer(campaign_term='testing') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_name(self): self.get_customer(campaign_name='testing') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_source(self): self.get_customer(campaign_source='testing') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_campaign_content(self): self.get_customer(campaign_content='testing') - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_customer_with_initial_bill_date(self): initial_bill_date = datetime.utcnow() + timedelta(days=60) @@ -216,12 +212,10 @@ def test_create_customer_with_initial_bill_date(self): diff = initial_bill_date.date() - real_bill_date.date() self.assertLessEqual(diff.days, 1) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer(self): self.get_customer(**self.paid_defaults) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_charges(self): data = copy(self.paid_defaults) @@ -231,7 +225,6 @@ def test_create_paid_customer_with_charges(self): data['charges'] = charges self.get_customer(**data) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_decimal_charges(self): data = copy(self.paid_defaults) @@ -241,7 +234,6 @@ def test_create_paid_customer_with_decimal_charges(self): data['charges'] = charges self.get_customer(**data) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_items(self): data = copy(self.paid_defaults) @@ -252,7 +244,6 @@ def test_create_paid_customer_with_items(self): data['plan_code'] = 'TRACKED_MONTHLY' self.get_customer(**data) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paid_customer_with_decimal_quantity_items(self): data = copy(self.paid_defaults) @@ -263,13 +254,11 @@ def test_create_paid_customer_with_decimal_quantity_items(self): data['plan_code'] = 'TRACKED_MONTHLY' self.get_customer(**data) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_create_paypal_customer(self): data = copy(self.paypal_defaults) self.get_customer(**data) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_update_paypal_customer(self): data = copy(self.paypal_defaults) @@ -280,7 +269,6 @@ def test_update_paypal_customer(self): cancel_url='http://example.com/update-cancel/', ) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_customer_repr(self): customer = self.get_customer() @@ -290,7 +278,6 @@ def test_customer_repr(self): self.assertEquals(expected, result) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_subscription_repr(self): customer = self.get_customer() @@ -301,7 +288,6 @@ def test_subscription_repr(self): self.assertIn(expected, result) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_pricing_plan_repr(self): customer = self.get_customer() @@ -313,7 +299,6 @@ def test_pricing_plan_repr(self): self.assertEquals(expected, result) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_item_repr(self): customer = self.get_customer_with_items() @@ -325,7 +310,6 @@ def test_item_repr(self): self.assertEquals(expected, result) - @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_get_customers(self): customer1 = self.get_customer() @@ -340,10 +324,8 @@ def test_get_customers(self): product = self.get_product() fetched_customers = product.get_customers() - self.assertEquals(2, len(fetched_customers)) - @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_get_customer(self): created_customer = self.get_customer() @@ -356,7 +338,6 @@ def test_get_customer(self): self.assertEquals(created_customer.last_name, fetched_customer.last_name) self.assertEquals(created_customer.email, fetched_customer.email) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_customer_update(self): new_name = 'Different' @@ -369,7 +350,6 @@ def test_simple_customer_update(self): fetched_customer = product.get_customer(code=customer.code) self.assertEquals(customer.first_name, fetched_customer.first_name) - @unittest.skip('Skip until deleting customers is working') @clear_users @raises(NotFound) def test_delete_customer(self): @@ -382,7 +362,7 @@ def test_delete_customer(self): customer.delete() fetched_customer = product.get_customer(code=customer.code) - @unittest.skip('Skip until deleting customers is working') + @clear_users def test_delete_all_customers(self): customer_1 = self.get_customer() @@ -397,7 +377,6 @@ def test_delete_all_customers(self): fetched_customers = product.get_customers() self.assertEquals(0, len(fetched_customers)) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_cancel_subscription(self): customer = self.get_customer() @@ -426,22 +405,18 @@ def assert_increment(self, quantity=None): fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_increment(self): self.assert_increment() - @unittest.skip('Skip until deleting customers is working') @clear_users def test_int_increment(self): self.assert_increment(1) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_increment(self): self.assert_increment(1.234) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_increment(self): self.assert_increment(Decimal('1.234')) @@ -462,22 +437,18 @@ def assert_decrement(self, quantity=None): fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_simple_decrement(self): self.assert_decrement() - @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_int_decrement(self): self.assert_decrement(1) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_decrement(self): self.assert_decrement(1.234) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_decrement(self): self.assert_decrement(Decimal('1.234')) @@ -497,17 +468,14 @@ def assert_set(self, quantity): fetched_item = customer.subscription.items[item.code] self.assertEquals(item.quantity_used, fetched_item.quantity_used) - @unittest.skip('Duplicate transaction error. 6 seconds.') @clear_users def test_int_set(self): self.assert_set(1) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_float_set(self): self.assert_set(1.234) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_decimal_set(self): self.assert_set(Decimal('1.234')) @@ -548,27 +516,22 @@ def assert_charged(self, code, each_amount, quantity=None, self.assertEqual(quantity, fetched_charge['quantity']) self.assertEqual(description, fetched_charge['description']) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_charge(self): self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_float_charge(self): self.assert_charged(code='TEST-CHARGE', each_amount=2.3, quantity=2) - - @unittest.skip('Skip until deleting customers is working') + @clear_users def test_add_decimal_charge(self): self.assert_charged(code='TEST-CHARGE', each_amount=Decimal('2.3'), quantity=3) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_charge_with_descriptions(self): self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1, description="A test charge") - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_credit(self): self.assert_charged(code='TEST-CHARGE', each_amount=-1, quantity=1) @@ -613,7 +576,6 @@ def assertOneTimeInvoice(self, charges): invoice_type = 'one-time', ) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_simple_one_time_invoice(self): charges = [{ @@ -624,7 +586,6 @@ def test_add_simple_one_time_invoice(self): self.assertOneTimeInvoice(charges) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_one_time_invoice_with_description(self): charges = [{ @@ -636,7 +597,6 @@ def test_add_one_time_invoice_with_description(self): self.assertOneTimeInvoice(charges) - @unittest.skip('Skip until deleting customers is working') @clear_users def test_add_one_time_invoice_with_multiple_charges(self): charges = [{ @@ -673,3 +633,23 @@ def test_get_promotion(self): 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) From 038ea9a678edbcaeca433c61db5b3cea8464d9a6 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 16:41:32 -0400 Subject: [PATCH 25/28] I hate to be that guy but OCD. PEP8 fixes. --- setup.py | 2 +- sharpy/client.py | 28 +- sharpy/exceptions.py | 28 +- sharpy/parsers.py | 285 ++++---- sharpy/product.py | 514 ++++++++------- tests/client_tests.py | 98 +-- tests/parser_tests.py | 1018 ++++++++++++++++------------- tests/product_tests.py | 263 ++++---- tests/testing_tools/decorators.py | 12 +- tests/testing_tools/utils.py | 9 +- 10 files changed, 1243 insertions(+), 1014 deletions(-) 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/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 52c7aa4..d0f6f60 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,18 @@ 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')) gateway_account_element = subscription_element.find('gatewayAccount') if gateway_account_element is not None: subscription['gateway_account'] = { @@ -242,91 +273,105 @@ 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 @@ -348,10 +393,13 @@ def parse_promotion(self, promotion_element): 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['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')) + promotion['incentives'] = self.parse_incentives( + promotion_element.find('incentives')) + promotion['coupons'] = self.parse_coupons( + promotion_element.find('coupons')) return promotion @@ -371,7 +419,8 @@ def parse_incentive(self, incentive_element): 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')) + incentive['created_datetime'] = self.parse_datetime( + incentive_element.findtext('createdDatetime')) return incentive @@ -390,7 +439,9 @@ def parse_coupon(self, coupon_element): 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')) + 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 817260d..a2e2414 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -1,6 +1,6 @@ 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 @@ -9,9 +9,11 @@ from sharpy.exceptions import NotFound 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,93 @@ 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, \ + + 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) - + 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) + 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, 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 +148,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 +172,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 @@ -202,19 +216,20 @@ def build_customer_post_data(self, code=None, first_name=None,\ 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,43 +241,45 @@ 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( path='customers/delete-all/confirm/%d' % int(time()), method='POST' ) + return self.load_data_from_xml(response.content) def get_all_promotions(self): ''' @@ -300,41 +317,41 @@ def get_promotion(self, code): 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 @@ -357,10 +374,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): ''' @@ -368,30 +385,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, \ + + 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): - + self.load_data(code=code, first_name=first_name, last_name=last_name, email=email, product=product, id=id, @@ -409,19 +425,18 @@ def __init__(self, code, first_name, last_name, email, product, id=None, \ created_datetime=created_datetime, modified_datetime=modified_datetime, meta_data=meta_data, - subscriptions=subscriptions - ) - + 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, \ + + 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): self.code = code self.id = id @@ -446,78 +461,73 @@ def load_data(self, code, first_name, last_name, email, product, id=None,\ 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, + 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,) + 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, + path=path, + params=params, ) - + return self.load_data_from_xml(response.content) + 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. ''' @@ -530,7 +540,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}, @@ -541,14 +551,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. @@ -562,7 +572,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(): @@ -574,49 +584,50 @@ 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) - + + 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) + 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, + 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): - + self.id = id self.gateway_token = gateway_token self.cc_first_name = cc_first_name @@ -647,7 +658,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(): @@ -656,7 +667,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) @@ -668,17 +679,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] @@ -686,27 +697,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 @@ -717,86 +729,86 @@ 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) diff --git a/tests/client_tests.py b/tests/client_tests.py index f3e612f..4841b3b 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,46 +8,53 @@ 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): client_kwargs = copy(self.client_defaults) client_kwargs.update(kwargs) - + c = Client(**client_kwargs) - + return c - + def try_client(self, **kwargs): 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): self.try_client() - + def test_custom_endpoint_init(self): - self.try_client(endpoint = 'http://cheddar-test.saaspire.com') - - + self.try_client(endpoint='http://cheddar-test.saaspire.com') + def try_url_build(self, path, params=None): c = self.get_client() expected = u'%s/%s/productCode/%s' % ( @@ -63,43 +69,42 @@ 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): path = 'users' self.try_url_build(path) - - + def test_single_param_build_url(self): path = 'users' params = {'key': 'value'} self.try_url_build(path, params) - + def test_multi_param_build_url(self): path = 'users' params = {'key1': 'value1', 'key2': 'value2'} self.try_url_build(path, params) - + def test_make_request(self): 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): path = 'plans/get' bad_username = self.client_defaults['username'] + '_bad' client = self.get_client(username=bad_username) client.make_request(path) - + @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): path = 'things-which-dont-exist' @@ -118,8 +123,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 +141,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 +156,12 @@ 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): assert_raises( expected_exception, self.generate_error_response, @@ -162,72 +169,75 @@ def assertCheddarError(self, auxcode, expected_exception, path=None, params=None path=path, params=params, ) - + def assertCheddarErrorForAuxCodes(self, auxcodes, expected_exception): for auxcode in auxcodes: self.assertCheddarError(auxcode, expected_exception) - + @clear_users def test_cheddar_500s(self): 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): auxcodes = (2000, 2001, 2002, 2003) expected_exception = AccessDenied self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception) - + @clear_users def test_cheddar_502s(self): auxcodes = (3000, 4000) expected_exception = NaughtyGateway self.assertCheddarErrorForAuxCodes(auxcodes, expected_exception) - + @clear_users def test_cheddar_422s(self): 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): self.generate_error_response(auxcode=2345, firstName='') - + def test_format_datetime_with_datetime(self): 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): 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): 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): client = self.get_client() result = client.format_date('now') @@ -257,4 +267,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..ecfd15c 100644 --- a/tests/parser_tests.py +++ b/tests/parser_tests.py @@ -7,40 +7,43 @@ 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): 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): parser = CheddarOutputParser() - + expected = True result = parser.parse_bool('1') - + self.assertEquals(expected, result) - + def test_bool_parsing_false(self): parser = CheddarOutputParser() - + expected = False result = parser.parse_bool('0') - + self.assertEquals(expected, result) - + @raises(ParseError) def test_bool_parsing_error(self): parser = CheddarOutputParser() - + parser.parse_bool('test') - + def test_bool_parsing_empty(self): parser = CheddarOutputParser() @@ -48,21 +51,21 @@ def test_bool_parsing_empty(self): result = parser.parse_bool('') self.assertEquals(expected, result) - + def test_int_parsing(self): parser = CheddarOutputParser() - + expected = 234 result = parser.parse_int('234') - + self.assertEquals(expected, result) - + @raises(ParseError) def test_int_parsing_error(self): parser = CheddarOutputParser() - + parser.parse_int('test') - + def test_int_parsing_empty(self): parser = CheddarOutputParser() @@ -70,21 +73,21 @@ def test_int_parsing_empty(self): result = parser.parse_int('') self.assertEquals(expected, result) - + def test_decimal_parsing(self): parser = CheddarOutputParser() - + expected = Decimal('2.345') result = parser.parse_decimal('2.345') - + self.assertEquals(expected, result) - + @raises(ParseError) def test_decimal_parsing_error(self): parser = CheddarOutputParser() - + parser.parse_decimal('test') - + def test_decimal_parsing_empty(self): parser = CheddarOutputParser() @@ -92,10 +95,10 @@ def test_decimal_parsing_empty(self): result = parser.parse_decimal('') self.assertEquals(expected, result) - + def test_datetime_parsing(self): parser = CheddarOutputParser() - + expected = datetime( year=2011, month=1, @@ -106,27 +109,26 @@ 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): parser = CheddarOutputParser() - + parser.parse_datetime('test') - + def test_datetime_parsing_empty(self): parser = CheddarOutputParser() - + expected = None result = parser.parse_datetime('') - + self.assertEquals(expected, result) - - + def test_error_parser(self): error_xml = self.load_file('error.xml') - + expected = { 'aux_code': '', 'code': '400', @@ -134,19 +136,22 @@ 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): 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 +165,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 +191,558 @@ def test_plans_parser(self): pp = pprint.PrettyPrinter(indent=4) pp.pprint(result) self.assertEquals(expected, result) - + def test_plans_parser_with_items(self): 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): 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': '', + '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): 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', + '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): 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': '', + '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 b834c8d..ae066d0 100644 --- a/tests/product_tests.py +++ b/tests/product_tests.py @@ -2,13 +2,12 @@ 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 @@ -17,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', @@ -47,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'), @@ -64,10 +64,10 @@ class ProductTests(unittest.TestCase): 'cc_zip': '12345', 'plan_code': 'PAID_MONTHLY', } - + def get_product(self): product = CheddarProduct(**self.client_defaults) - + return product def test_repr(self): @@ -79,15 +79,15 @@ def test_repr(self): def test_instantiate_product(self): 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): product = self.get_product() - + plans = product.get_all_plans() - + for plan in plans: if plan.code == 'FREE_MONTHLY': free_plan = plan @@ -95,28 +95,28 @@ 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): product = self.get_product() code = 'PAID_MONTHLY' plan = product.get_plan(code) - + self.assertEquals(code, plan.code) - + def test_plan_initial_bill_date(self): 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): customer_data = copy(self.customer_defaults) # We need to make unique customers with the same data. @@ -128,11 +128,11 @@ def get_customer(self, **kwargs): 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): data = copy(self.paid_defaults) if 'items' in kwargs.keys(): @@ -141,11 +141,11 @@ 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 @@ -158,7 +158,7 @@ def test_create_customer_with_company(self): @clear_users def test_create_customer_with_meta_data(self): - self.get_customer(meta_data = {'key_1': 'value_1', 'key2': 'value_2'}) + self.get_customer(meta_data={'key_1': 'value_1', 'key2': 'value_2'}) @clear_users def test_create_customer_with_true_vat_exempt(self): @@ -206,8 +206,8 @@ def test_create_customer_with_initial_bill_date(self): 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) @@ -220,7 +220,7 @@ def test_create_paid_customer(self): def test_create_paid_customer_with_charges(self): 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) @@ -229,7 +229,8 @@ def test_create_paid_customer_with_charges(self): def test_create_paid_customer_with_decimal_charges(self): 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) @@ -282,10 +283,10 @@ def test_customer_repr(self): def test_subscription_repr(self): customer = self.get_customer() subscription = customer.subscription - + expected = 'Subscription:' result = repr(subscription) - + self.assertIn(expected, result) @clear_users @@ -293,10 +294,10 @@ def test_pricing_plan_repr(self): 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 @@ -304,25 +305,26 @@ def test_item_repr(self): 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() - + fetched_customers = product.get_customers() self.assertEquals(2, len(fetched_customers)) @@ -330,12 +332,14 @@ def test_get_customers(self): def test_get_customer(self): 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 @@ -343,10 +347,10 @@ def test_simple_customer_update(self): 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) @@ -355,25 +359,28 @@ def test_simple_customer_update(self): def test_delete_customer(self): 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() - + 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)) @@ -381,28 +388,28 @@ def test_delete_all_customers(self): def test_cancel_subscription(self): 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): 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 @@ -420,21 +427,21 @@ def test_float_increment(self): @clear_users def test_decimal_increment(self): self.assert_increment(Decimal('1.234')) - + def assert_decrement(self, quantity=None): 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 @@ -452,20 +459,19 @@ def test_float_decrement(self): @clear_users def test_decimal_decrement(self): self.assert_decrement(Decimal('1.234')) - + def assert_set(self, quantity): 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 @@ -479,40 +485,42 @@ def test_float_set(self): @clear_users def test_decimal_set(self): self.assert_set(Decimal('1.234')) - + def assert_charged(self, code, each_amount, quantity=None, - description=None): + description=None): 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']) @@ -526,29 +534,32 @@ def test_add_float_charge(self): @clear_users def test_add_decimal_charge(self): - self.assert_charged(code='TEST-CHARGE', each_amount=Decimal('2.3'), quantity=3) + 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") + self.assert_charged(code='TEST-CHARGE', each_amount=1, quantity=1, + description="A test charge") @clear_users def test_add_credit(self): 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): 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): customer = self.get_customer(**self.paid_defaults) product = self.get_product() @@ -558,59 +569,62 @@ 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) - },] + 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' - },] + 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' - },] + 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) @@ -632,7 +646,8 @@ def test_get_promotion(self): 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) + self.assertEqual(promotion.incentives[0].get('expiration_datetime'), + None) def test_promotion_repr(self): ''' Test the internal __repr___ method of Promotion. ''' @@ -641,7 +656,7 @@ def test_promotion_repr(self): expected = 'Promotion: Coupon (COUPON)' result = repr(promotion) - + self.assertEquals(expected, result) def test_promotion_unicode(self): @@ -651,5 +666,5 @@ def test_promotion_unicode(self): expected = 'Coupon (COUPON)' result = unicode(promotion) - + self.assertEquals(expected, result) 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 0b5d616..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 or 'success' not in content: - raise Exception('Could not clear users. Recieved a response of %s %s \n %s' % (response.status, response.reason, content)) + raise Exception( + 'Could not clear users. Recieved a response of %s %s \n %s' % ( + response.status, response.reason, content)) From a8032478f0ae4334738e96ede4af421bf4c92c80 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 17:45:27 -0400 Subject: [PATCH 26/28] Tried to add a return and failed due to the PEP8 on the unused response. We just won't store it. --- sharpy/product.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sharpy/product.py b/sharpy/product.py index a2e2414..80bb296 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -275,11 +275,10 @@ def delete_all_customers(self): 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' ) - return self.load_data_from_xml(response.content) def get_all_promotions(self): ''' @@ -517,11 +516,10 @@ def update(self, first_name=None, last_name=None, email=None, def delete(self): path = 'customers/delete' params = {'code': self.code} - response = self.product.client.make_request( + self.product.client.make_request( path=path, params=params, ) - return self.load_data_from_xml(response.content) def charge(self, code, each_amount, quantity=1, description=None): ''' From b18e79446b4cbe38140bcde382199a043f1b9390 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Thu, 27 Aug 2015 17:46:12 -0400 Subject: [PATCH 27/28] Add docstrings to the tests. Also add a sleep for a test that fails sparadically. I think network traffic or slowness on chedder servers is at play. --- tests/client_tests.py | 30 +++++++++++++++-- tests/parser_tests.py | 20 ++++++++++++ tests/product_tests.py | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/tests/client_tests.py b/tests/client_tests.py index 4841b3b..370da62 100644 --- a/tests/client_tests.py +++ b/tests/client_tests.py @@ -28,6 +28,7 @@ class ClientTests(unittest.TestCase): } def get_client(self, **kwargs): + ''' Helper mthod for instantiating client. ''' client_kwargs = copy(self.client_defaults) client_kwargs.update(kwargs) @@ -36,6 +37,7 @@ def get_client(self, **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) @@ -50,12 +52,15 @@ def try_client(self, **kwargs): self.assertEquals(client.default_endpoint, client.endpoint) def test_basic_init(self): + ''' Test basic initialization. ''' self.try_client() def test_custom_endpoint_init(self): + ''' 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, @@ -71,20 +76,24 @@ def try_url_build(self, path, params=None): 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) @@ -93,6 +102,7 @@ def test_make_request(self): @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) @@ -100,19 +110,21 @@ def test_make_request_access_denied(self): @raises(NotFound) def test_make_request_bad_request(self): - """ Attempt to grab the plans without adding /get to the url. """ + ''' 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', @@ -162,6 +174,7 @@ def generate_error_response(self, auxcode=None, 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, @@ -171,11 +184,13 @@ def assertCheddarError(self, auxcode, expected_exception, path=None, ) 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) @@ -193,18 +208,21 @@ def test_cheddar_400(self): @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) @@ -212,9 +230,11 @@ def test_cheddar_422s(self): @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)) @@ -223,6 +243,7 @@ def test_format_datetime_with_datetime(self): 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, @@ -232,6 +253,7 @@ def test_format_datetime_with_datetime_with_tz(self): 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)) expected = '2010-09-19T00:00:00+00:00' @@ -239,6 +261,7 @@ def test_format_datetime_with_date(self): 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' @@ -246,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' @@ -254,9 +278,9 @@ def test_format_datetime_with_now(self): @clear_users def test_chedder_update_customer_error(self): - """ + ''' Test overriding the zipcode so a customer actually gets updated. - """ + ''' overrides = { 'subscription[ccZip]': 12345 } diff --git a/tests/parser_tests.py b/tests/parser_tests.py index ecfd15c..9ab3a37 100644 --- a/tests/parser_tests.py +++ b/tests/parser_tests.py @@ -16,6 +16,7 @@ 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() @@ -23,6 +24,7 @@ def load_file(self, filename): return content def test_bool_parsing_true(self): + ''' Test boolean parsing evaluates to true. ''' parser = CheddarOutputParser() expected = True @@ -31,6 +33,7 @@ def test_bool_parsing_true(self): self.assertEquals(expected, result) def test_bool_parsing_false(self): + ''' Test boolean parsing evaluates to false. ''' parser = CheddarOutputParser() expected = False @@ -40,11 +43,13 @@ def test_bool_parsing_false(self): @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 @@ -53,6 +58,7 @@ def test_bool_parsing_empty(self): self.assertEquals(expected, result) def test_int_parsing(self): + ''' Test integer parsing with integer as string. ''' parser = CheddarOutputParser() expected = 234 @@ -62,11 +68,13 @@ def test_int_parsing(self): @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 @@ -75,6 +83,7 @@ def test_int_parsing_empty(self): self.assertEquals(expected, result) def test_decimal_parsing(self): + ''' Test decimal parsing with decimal string. ''' parser = CheddarOutputParser() expected = Decimal('2.345') @@ -84,11 +93,13 @@ def test_decimal_parsing(self): @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 @@ -97,6 +108,7 @@ def test_decimal_parsing_empty(self): self.assertEquals(expected, result) def test_datetime_parsing(self): + ''' Test datetime parsing. ''' parser = CheddarOutputParser() expected = datetime( @@ -114,11 +126,13 @@ def test_datetime_parsing(self): @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 @@ -127,6 +141,7 @@ def test_datetime_parsing_empty(self): self.assertEquals(expected, result) def test_error_parser(self): + ''' Test error parser. ''' error_xml = self.load_file('error.xml') expected = { @@ -140,6 +155,7 @@ def test_error_parser(self): self.assertEquals(expected, result) def test_plans_parser(self): + ''' Test plans parser. ''' plans_xml = self.load_file('plans.xml') parser = PlansParser() @@ -193,6 +209,7 @@ def test_plans_parser(self): 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() @@ -322,6 +339,7 @@ def test_plans_parser_with_items(self): 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() @@ -443,6 +461,7 @@ def test_customers_parser_with_no_items(self): 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() @@ -606,6 +625,7 @@ def test_customers_parser_with_items(self): self.assertEquals(expected, result) def test_paypal_customer_parse(self): + ''' Test customer parser with paypal customer. ''' customers_xml = self.load_file('paypal_customer.xml') parser = CustomersParser() diff --git a/tests/product_tests.py b/tests/product_tests.py index ae066d0..2266398 100644 --- a/tests/product_tests.py +++ b/tests/product_tests.py @@ -66,11 +66,13 @@ class ProductTests(unittest.TestCase): } 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__() @@ -78,12 +80,14 @@ 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() @@ -101,6 +105,7 @@ def test_get_all_plans(self): 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) @@ -108,6 +113,7 @@ def test_get_plan(self): 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) @@ -118,6 +124,7 @@ def test_plan_initial_bill_date(self): 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. @@ -134,6 +141,7 @@ def get_customer(self, **kwargs): 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'] @@ -150,58 +158,72 @@ def get_customer_with_items(self, **kwargs): @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): + ''' 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] @@ -214,10 +236,12 @@ def test_create_customer_with_initial_bill_date(self): @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}) @@ -227,6 +251,9 @@ def test_create_paid_customer_with_charges(self): @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', @@ -237,6 +264,7 @@ def test_create_paid_customer_with_decimal_charges(self): @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}) @@ -247,6 +275,10 @@ def test_create_paid_customer_with_items(self): @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')}) @@ -257,11 +289,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( @@ -272,6 +306,7 @@ def test_update_paypal_customer(self): @clear_users def test_customer_repr(self): + ''' Test Customer __repr__ method. ''' customer = self.get_customer() expected = 'Customer: Test User (test)' @@ -281,6 +316,7 @@ def test_customer_repr(self): @clear_users def test_subscription_repr(self): + ''' Test Subscription __repr__ method. ''' customer = self.get_customer() subscription = customer.subscription @@ -291,6 +327,7 @@ def test_subscription_repr(self): @clear_users def test_pricing_plan_repr(self): + ''' Test PricingPlan __repr__ method. ''' customer = self.get_customer() subscription = customer.subscription plan = subscription.plan @@ -302,6 +339,7 @@ def test_pricing_plan_repr(self): @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'] @@ -330,6 +368,7 @@ def test_get_customers(self): @clear_users def test_get_customer(self): + ''' Test getting a customer by code.. ''' created_customer = self.get_customer() product = self.get_product() @@ -344,6 +383,7 @@ def test_get_customer(self): @clear_users def test_simple_customer_update(self): + ''' Test Update Customer. ''' new_name = 'Different' customer = self.get_customer() product = self.get_product() @@ -357,6 +397,7 @@ def test_simple_customer_update(self): @clear_users @raises(NotFound) def test_delete_customer(self): + ''' Create a Customer and delete that customer. ''' customer = self.get_customer() product = self.get_product() @@ -376,6 +417,10 @@ def test_delete_all_customers(self): 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)) @@ -386,6 +431,7 @@ def test_delete_all_customers(self): @clear_users def test_cancel_subscription(self): + ''' Test cancel subscription. ''' customer = self.get_customer() customer.subscription.cancel() @@ -397,6 +443,7 @@ def test_cancel_subscription(self): 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'] @@ -414,21 +461,26 @@ def assert_increment(self, quantity=None): @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'] @@ -446,21 +498,28 @@ def assert_decrement(self, quantity=None): @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'] @@ -476,18 +535,22 @@ def assert_set(self, quantity): @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): + ''' Helper method for asserting custom charges as expected. ''' customer = self.get_customer(**self.paid_defaults) product = self.get_product() @@ -526,28 +589,34 @@ def assert_charged(self, code, each_amount, quantity=None, @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): + ''' 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): + ''' 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): + ''' 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): + ''' 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: @@ -561,6 +630,7 @@ def assertCharge(self, customer, code, each_amount, 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() @@ -589,6 +659,7 @@ def assertOneTimeInvoice(self, charges): @clear_users def test_add_simple_one_time_invoice(self): + ''' Test adding a one time invoice. ''' charges = [ { 'code': 'immediate-test', @@ -600,6 +671,7 @@ def test_add_simple_one_time_invoice(self): @clear_users def test_add_one_time_invoice_with_description(self): + ''' Test adding a one time invoice with description. ''' charges = [ { 'code': 'immediate-test', @@ -612,6 +684,7 @@ def test_add_one_time_invoice_with_description(self): @clear_users def test_add_one_time_invoice_with_multiple_charges(self): + ''' Test adding a one time invoice with multiple charges. ''' charges = [ { 'code': 'immediate-test', From 043620ff0698b719e40e6c76321121e6741590d7 Mon Sep 17 00:00:00 2001 From: Ryan Johnston Date: Fri, 28 Aug 2015 14:18:32 -0400 Subject: [PATCH 28/28] Add coupon_code to customer create and update methods. With Testing. --- sharpy/parsers.py | 2 ++ sharpy/product.py | 37 +++++++++++++++++++++++-------------- tests/parser_tests.py | 3 +++ tests/product_tests.py | 11 +++++++++++ 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/sharpy/parsers.py b/sharpy/parsers.py index d0f6f60..846fcd7 100644 --- a/sharpy/parsers.py +++ b/sharpy/parsers.py @@ -266,6 +266,8 @@ def parse_subscription(self, subscription_element): 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'] = { diff --git a/sharpy/product.py b/sharpy/product.py index 80bb296..188b80a 100644 --- a/sharpy/product.py +++ b/sharpy/product.py @@ -59,8 +59,9 @@ def create_customer(self, code, first_name, last_name, email, plan_code, 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): + 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, @@ -75,7 +76,8 @@ def create_customer(self, code, first_name, last_name, email, plan_code, cc_last_name, cc_email, cc_company, cc_country, cc_address, cc_city, cc_state, - cc_zip, return_url, cancel_url) + cc_zip, coupon_code, return_url, + cancel_url) if charges: for i, charge in enumerate(charges): @@ -112,8 +114,9 @@ def build_customer_post_data(self, code=None, 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): + cc_state=None, cc_zip=None, coupon_code=None, + return_url=None, cancel_url=None, + bill_date=None): data = {} @@ -214,6 +217,9 @@ 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 @@ -405,7 +411,7 @@ def __init__(self, code, first_name, last_name, email, product, id=None, campaign_medium=None, campaign_term=None, campaign_content=None, campaign_name=None, created_datetime=None, modified_datetime=None, - meta_data=None, subscriptions=None): + coupon_code=None, meta_data=None, subscriptions=None): self.load_data(code=code, first_name=first_name, last_name=last_name, @@ -423,7 +429,7 @@ 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, + coupon_code=coupon_code, meta_data=meta_data, subscriptions=subscriptions) super(Customer, self).__init__() @@ -436,7 +442,7 @@ def load_data(self, code, first_name, last_name, email, product, id=None, campaign_medium=None, campaign_term=None, campaign_content=None, campaign_name=None, created_datetime=None, modified_datetime=None, - meta_data=None, subscriptions=None): + coupon_code=None, meta_data=None, subscriptions=None): self.code = code self.id = id self.first_name = first_name @@ -457,6 +463,7 @@ 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: @@ -486,7 +493,7 @@ def update(self, first_name=None, last_name=None, email=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,): + 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, @@ -500,8 +507,8 @@ def update(self, first_name=None, last_name=None, email=None, 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,) + 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} @@ -599,7 +606,7 @@ def __init__(self, id, gateway_token, cc_first_name, cc_last_name, 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): + coupon_code=None, redirect_url=None): self.load_data(id=id, gateway_token=gateway_token, cc_first_name=cc_first_name, @@ -615,7 +622,7 @@ def __init__(self, id, gateway_token, cc_first_name, cc_last_name, invoices=invoices, items=items, gateway_account=gateway_account, cancel_reason=cancel_reason, cancel_type=cancel_type, - redirect_url=redirect_url) + coupon_code=coupon_code, redirect_url=redirect_url) super(Subscription, self).__init__() @@ -624,7 +631,8 @@ def load_data(self, id, gateway_token, cc_first_name, cc_last_name, 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): + cancel_reason=None, cancel_type=None, coupon_code=None, + redirect_url=None): self.id = id self.gateway_token = gateway_token @@ -647,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 diff --git a/tests/parser_tests.py b/tests/parser_tests.py index 9ab3a37..ae00e68 100644 --- a/tests/parser_tests.py +++ b/tests/parser_tests.py @@ -389,6 +389,7 @@ def test_customers_parser_with_no_items(self): 'cancel_type': None, 'canceled_datetime': None, 'cc_address': '', + 'coupon_code': None, 'cc_city': '', 'cc_company': '', 'cc_country': '', @@ -495,6 +496,7 @@ def test_customers_parser_with_items(self): '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', @@ -660,6 +662,7 @@ def test_paypal_customer_parse(self): 'canceled_datetime': datetime(2011, 5, 16, 16, 36, 1, tzinfo=tzutc()), 'cc_address': '', + 'coupon_code': None, 'cc_city': '', 'cc_company': '', 'cc_country': '', diff --git a/tests/product_tests.py b/tests/product_tests.py index 2266398..95d4f20 100644 --- a/tests/product_tests.py +++ b/tests/product_tests.py @@ -249,6 +249,13 @@ def test_create_paid_customer_with_charges(self): 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): ''' @@ -363,6 +370,10 @@ def test_get_customers(self): 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))