diff --git a/r2/r2/lib/authorize/__init__.py b/r2/r2/lib/authorize/__init__.py index f13e13984..0cd832e51 100644 --- a/r2/r2/lib/authorize/__init__.py +++ b/r2/r2/lib/authorize/__init__.py @@ -20,4 +20,5 @@ # Inc. All Rights Reserved. ############################################################################### +from api import * from interaction import * diff --git a/r2/r2/lib/authorize/api.py b/r2/r2/lib/authorize/api.py index 5165b1ef8..c0f797054 100644 --- a/r2/r2/lib/authorize/api.py +++ b/r2/r2/lib/authorize/api.py @@ -27,28 +27,33 @@ This file consists mostly of wrapper classes for dealing with their API, while the actual useful functions live in interaction.py """ -from pylons import g +import re +import socket from httplib import HTTPSConnection from urlparse import urlparse -import socket, re + from BeautifulSoup import BeautifulStoneSoup -from r2.lib.utils import iters, Storage - -from r2.models import NotFound -from r2.models.bidding import CustomerID, PayID, ShippingAddress - +from pylons import g from xml.sax.saxutils import escape +from r2.lib.export import export +from r2.lib.utils import iters, Storage +from r2.models.bidding import CustomerID, PayID, ShippingAddress + +__all__ = ["PROFILE_LIMIT"] + + # list of the most common errors. -Errors = Storage(TESTMODE = "E00009", - TRANSACTION_FAIL = "E00027", - DUPLICATE_RECORD = "E00039", - RECORD_NOT_FOUND = "E00040", - TOO_MANY_PAY_PROFILES = "E00042", - TOO_MANY_SHIP_ADDRESSES = "E00043") +Errors = Storage(TESTMODE="E00009", + TRANSACTION_FAIL="E00027", + DUPLICATE_RECORD="E00039", + RECORD_NOT_FOUND="E00040", + TOO_MANY_PAY_PROFILES="E00042", + TOO_MANY_SHIP_ADDRESSES="E00043") PROFILE_LIMIT = 10 # max payment profiles per user allowed by authorize.net +@export class AuthorizeNetException(Exception): def __init__(self, msg): # don't let CC info show up in logs @@ -65,6 +70,7 @@ class AuthorizeNetException(Exception): # xml tags whose content shouldn't be escaped _no_escape_list = ["extraOptions"] + class SimpleXMLObject(object): """ All API transactions are done with authorize.net using XML, so @@ -81,9 +87,10 @@ class SimpleXMLObject(object): @staticmethod def simple_tag(name, content, **attrs): attrs = " ".join('%s="%s"' % (k, v) for k, v in attrs.iteritems()) - if attrs: attrs = " " + attrs + if attrs: + attrs = " " + attrs return ("<%(name)s%(attrs)s>%(content)s" % - dict(name = name, content = content, attrs = attrs)) + dict(name=name, content=content, attrs=attrs)) def toXML(self): content = [] @@ -128,9 +135,12 @@ class SimpleXMLObject(object): def _wrapper(self, content): return content + class Auth(SimpleXMLObject): _keys = ["name", "transactionKey"] + +@export class Address(SimpleXMLObject): _keys = ["firstName", "lastName", "company", "address", "city", "state", "zip", "country", "phoneNumber", @@ -144,11 +154,11 @@ class Address(SimpleXMLObject): SimpleXMLObject.__init__(self, **kw) +@export class CreditCard(SimpleXMLObject): _keys = ["cardNumber", "expirationDate", "cardCode"] - class Profile(SimpleXMLObject): """ Converts a user into a Profile object. @@ -157,23 +167,22 @@ class Profile(SimpleXMLObject): "email", "customerProfileId", "paymentProfiles", "shipToList", "validationMode"] def __init__(self, user, paymentProfiles, address, - validationMode = None): - SimpleXMLObject.__init__(self, merchantCustomerId = user._fullname, - description = user.name, email = "", - paymentProfiles = paymentProfiles, - shipToList = address, - validationMode = validationMode, + validationMode=None): + SimpleXMLObject.__init__(self, merchantCustomerId=user._fullname, + description=user.name, email="", + paymentProfiles=paymentProfiles, + shipToList=address, + validationMode=validationMode, customerProfileId=CustomerID.get_id(user)) - class PaymentProfile(SimpleXMLObject): _keys = ["billTo", "payment", "customerPaymentProfileId", "validationMode"] - def __init__(self, billTo, card, paymentId = None, - validationMode = None): - SimpleXMLObject.__init__(self, billTo = billTo, - customerPaymentProfileId = paymentId, - payment = SimpleXMLObject(creditCard = card), - validationMode = validationMode) + def __init__(self, billTo, card, paymentId=None, + validationMode=None): + SimpleXMLObject.__init__(self, billTo=billTo, + customerPaymentProfileId=paymentId, + payment=SimpleXMLObject(creditCard=card), + validationMode=validationMode) @classmethod def fromXML(cls, res): @@ -181,38 +190,56 @@ class PaymentProfile(SimpleXMLObject): return cls(Address.fromXML(res.billto), CreditCard.fromXML(res.payment), payid) + +@export class Order(SimpleXMLObject): _keys = ["invoiceNumber", "description", "purchaseOrderNumber"] + class Transaction(SimpleXMLObject): _keys = ["amount", "customerProfileId", "customerPaymentProfileId", "transId", "order"] - def __init__(self, amount, profile_id, pay_id, trans_id = None, - order = None): - SimpleXMLObject.__init__(self, amount = amount, - customerProfileId = profile_id, - customerPaymentProfileId = pay_id, - transId = trans_id, - order = order) + def __init__(self, amount, profile_id, pay_id, trans_id=None, + order=None): + SimpleXMLObject.__init__(self, amount=amount, + customerProfileId=profile_id, + customerPaymentProfileId=pay_id, + transId=trans_id, + order=order) def _wrapper(self, content): return self.simple_tag(self._name(), content) + # authorize and charge +@export class ProfileTransAuthCapture(Transaction): pass -# only authorize (no charge is made) -class ProfileTransAuthOnly(Transaction): pass -# charge only (requires previous auth_only) -class ProfileTransPriorAuthCapture(Transaction): pass -# stronger than above: charge even on decline (not sure why you would want to) -class ProfileTransCaptureOnly(Transaction): pass -# refund a transaction -class ProfileTransRefund(Transaction): pass -# void a transaction -class ProfileTransVoid(Transaction): pass +# only authorize (no charge is made) +@export +class ProfileTransAuthOnly(Transaction): pass + + +# charge only (requires previous auth_only) +@export +class ProfileTransPriorAuthCapture(Transaction): pass + + +# stronger than above: charge even on decline (not sure why you would want to) +@export +class ProfileTransCaptureOnly(Transaction): pass + + +# refund a transaction +@export +class ProfileTransRefund(Transaction): pass + + +# void a transaction +@export +class ProfileTransVoid(Transaction): pass #----- @@ -221,8 +248,8 @@ class AuthorizeNetRequest(SimpleXMLObject): @property def merchantAuthentication(self): - return Auth(name = g.authorizenetname, - transactionKey = g.authorizenetkey) + return Auth(name=g.authorizenetname, + transactionKey=g.authorizenetkey) def _wrapper(self, content): return ('' + @@ -253,7 +280,7 @@ class AuthorizeNetRequest(SimpleXMLObject): _autoclose_re = re.compile("<([^/]+)/>") def _autoclose_handler(self, m): - return "<%(m)s>" % dict(m = m.groups()[0]) + return "<%(m)s>" % dict(m=m.groups()[0]) def handle_response(self, res): res = self._autoclose_re.sub(self._autoclose_handler, res) @@ -277,11 +304,11 @@ class CustomerRequest(AuthorizeNetRequest): else: cust_id = CustomerID.get_id(user) self._user = user - AuthorizeNetRequest.__init__(self, customerProfileId = cust_id, **kw) - + AuthorizeNetRequest.__init__(self, customerProfileId=cust_id, **kw) # --- real request classes below + class CreateCustomerProfileRequest(AuthorizeNetRequest): """ Create a new user object on authorize.net and return the new object ID. @@ -291,12 +318,12 @@ class CreateCustomerProfileRequest(AuthorizeNetRequest): """ _keys = AuthorizeNetRequest._keys + ["profile", "validationMode"] - def __init__(self, user, validationMode = None): + def __init__(self, user, validationMode=None): # cache the user object passed in self._user = user AuthorizeNetRequest.__init__(self, - profile = Profile(user, None, None), - validationMode = validationMode) + profile=Profile(user, None, None), + validationMode=validationMode) def process_response(self, res): customer_id = int(res.customerprofileid.contents[0]) @@ -330,6 +357,7 @@ class CreateCustomerProfileRequest(AuthorizeNetRequest): return cust_id return AuthorizeNetRequest.process_error(self, res) + class CreateCustomerPaymentProfileRequest(CustomerRequest): """ Adds a payment profile to an existing user object. The profile @@ -337,11 +365,11 @@ class CreateCustomerPaymentProfileRequest(CustomerRequest): """ _keys = (CustomerRequest._keys + ["paymentProfile", "validationMode"]) - def __init__(self, user, address, creditcard, validationMode = None): + def __init__(self, user, address, creditcard, validationMode=None): CustomerRequest.__init__(self, user, - paymentProfile = PaymentProfile(address, - creditcard), - validationMode = validationMode) + paymentProfile=PaymentProfile(address, + creditcard), + validationMode=validationMode) def process_response(self, res): pay_id = int(res.customerpaymentprofileid.contents[0]) @@ -355,7 +383,8 @@ class CreateCustomerPaymentProfileRequest(CustomerRequest): if len(profiles) == 1: return profiles[0].customerPaymentProfileId return - return CustomerRequest.process_error(self,res) + return CustomerRequest.process_error(self, res) + class CreateCustomerShippingAddressRequest(CustomerRequest): """ @@ -396,7 +425,7 @@ class GetCustomerPaymentProfileRequest(CustomerRequest): def process_error(self, res): if self.is_error_code(res, Errors.RECORD_NOT_FOUND): PayID.delete(self._user, self.customerPaymentProfileId) - return CustomerRequest.process_error(self,res) + return CustomerRequest.process_error(self, res) class GetCustomerShippingAddressRequest(CustomerRequest): @@ -418,7 +447,8 @@ class GetCustomerShippingAddressRequest(CustomerRequest): def process_error(self, res): if self.is_error_code(res, Errors.RECORD_NOT_FOUND): ShippingAddress.delete(self._user, self.customerAddressId) - return CustomerRequest.process_error(self,res) + return CustomerRequest.process_error(self, res) + class GetCustomerProfileIdsRequest(AuthorizeNetRequest): """ @@ -428,6 +458,7 @@ class GetCustomerProfileIdsRequest(AuthorizeNetRequest): def process_response(self, res): return [int(x.contents[0]) for x in res.ids.findAll('numericstring')] + class GetCustomerProfileRequest(CustomerRequest): """ Given a user, find their customer information. @@ -458,7 +489,7 @@ class GetCustomerProfileRequest(CustomerRequest): for profile in res.findAll("paymentprofiles"): a = Address.fromXML(profile) cc = CreditCard.fromXML(profile.payment) - payprof = PaymentProfile(a, cc,int(a.customerPaymentProfileId)) + payprof = PaymentProfile(a, cc, int(a.customerPaymentProfileId)) PayID.add(acct, a.customerPaymentProfileId) profiles.append(payprof) @@ -476,20 +507,22 @@ class DeleteCustomerProfileRequest(CustomerRequest): def process_error(self, res): if self.is_error_code(res, Errors.RECORD_NOT_FOUND): CustomerID.delete(self._user) - return CustomerRequest.process_error(self,res) + return CustomerRequest.process_error(self, res) + class DeleteCustomerPaymentProfileRequest(GetCustomerPaymentProfileRequest): """ Delete a customer shipping address """ def process_response(self, res): - PayID.delete(self._user,self.customerPaymentProfileId) + PayID.delete(self._user, self.customerPaymentProfileId) return True def process_error(self, res): if self.is_error_code(res, Errors.RECORD_NOT_FOUND): - PayID.delete(self._user,self.customerPaymentProfileId) - return GetCustomerPaymentProfileRequest.process_error(self,res) + PayID.delete(self._user, self.customerPaymentProfileId) + return GetCustomerPaymentProfileRequest.process_error(self, res) + class DeleteCustomerShippingAddressRequest(GetCustomerShippingAddressRequest): """ @@ -502,9 +535,7 @@ class DeleteCustomerShippingAddressRequest(GetCustomerShippingAddressRequest): def process_error(self, res): if self.is_error_code(res, Errors.RECORD_NOT_FOUND): ShippingAddress.delete(self._user, self.customerAddressId) - GetCustomerShippingAddressRequest.process_error(self,res) - - + GetCustomerShippingAddressRequest.process_error(self, res) # TODO @@ -520,12 +551,12 @@ class UpdateCustomerPaymentProfileRequest(CreateCustomerPaymentProfileRequest): For updating the user's payment profile """ def __init__(self, user, paymentid, address, creditcard, - validationMode = None): + validationMode=None): CustomerRequest.__init__(self, user, paymentProfile=PaymentProfile(address, creditcard, paymentid), - validationMode = validationMode) + validationMode=validationMode) def process_response(self, res): return self.paymentProfile.customerPaymentProfileId @@ -539,14 +570,12 @@ class UpdateCustomerShippingAddressRequest( def __init__(self, user, address_id, address): address.customerAddressId = address_id CreateCustomerShippingAddressRequest.__init__(self, user, - address = address) + address=address) def process_response(self, res): return True - - class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest): _keys = AuthorizeNetRequest._keys + ["transaction", "extraOptions"] @@ -579,10 +608,10 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest): "cav_response") # list of casts for the response fields given above - response_types = dict(response_code = int, - response_subcode = int, - response_reason_code = int, - trans_id = int) + response_types = dict(response_code=int, + response_subcode=int, + response_reason_code=int, + trans_id=int) def __init__(self, **kw): from pylons import g @@ -604,7 +633,7 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest): return (False, self.package_response(res)) elif self.is_error_code(res, Errors.TESTMODE): return (None, None) - return AuthorizeNetRequest.process_error(self,res) + return AuthorizeNetRequest.process_error(self, res) def package_response(self, res): @@ -617,6 +646,7 @@ class CreateCustomerProfileTransactionRequest(AuthorizeNetRequest): pass return s + class GetSettledBatchListRequest(AuthorizeNetRequest): _keys = AuthorizeNetRequest._keys + ["includeStatistics", "firstSettlementDate", diff --git a/r2/r2/lib/authorize/interaction.py b/r2/r2/lib/authorize/interaction.py index 26abc8e4a..33978ec3f 100644 --- a/r2/r2/lib/authorize/interaction.py +++ b/r2/r2/lib/authorize/interaction.py @@ -20,9 +20,29 @@ # Inc. All Rights Reserved. ############################################################################### -from api import * from pylons import g -from r2.models.bidding import Bid + +from r2.lib.db.thing import NotFound +from r2.lib.utils import Storage +from r2.lib.export import export +from r2.models.bidding import Bid, CustomerID, PayID + +from r2.lib.authorize.api import ( + Address, + AuthorizeNetException, + CreateCustomerPaymentProfileRequest, + CreateCustomerProfileRequest, + CreateCustomerProfileTransactionRequest, + CreditCard, + GetCustomerProfileRequest, + Order, + ProfileTransAuthOnly, + ProfileTransPriorAuthCapture, + ProfileTransVoid, + UpdateCustomerPaymentProfileRequest, +) + +__all__ = [] # useful test data: test_card = dict(AMEX = ("370000000000002" , 1234), @@ -32,15 +52,21 @@ test_card = dict(AMEX = ("370000000000002" , 1234), # visa card which generates error codes based on the amount ERRORCARD = ("4222222222222" , 123)) -test_card = Storage((k, CreditCard(cardNumber = x, expirationDate="2011-11", - cardCode = y)) for k, (x, y) in +test_card = Storage((k, CreditCard(cardNumber=x, + expirationDate="2011-11", + cardCode=y)) for k, (x, y) in test_card.iteritems()) -test_address = Address(firstName = "John", lastName = "Doe", - address = "123 Fake St.", - city = "Anytown", state = "MN", zip = "12346") +test_address = Address(firstName="John", + lastName="Doe", + address="123 Fake St.", + city="Anytown", + state="MN", + zip="12346") -def get_account_info(user, recursed = False): + +@export +def get_account_info(user, recursed=False): # if we don't have an ID for the user, try to make one if not CustomerID.get_id(user): cust_id = CreateCustomerProfileRequest(user).make_request() @@ -61,7 +87,9 @@ def get_account_info(user, recursed = False): raise AuthorizeNetException, "error creating user" return data -def edit_profile(user, address, creditcard, pay_id = None): + +@export +def edit_profile(user, address, creditcard, pay_id=None): if pay_id: return UpdateCustomerPaymentProfileRequest( user, pay_id, address, creditcard).make_request() @@ -70,10 +98,8 @@ def edit_profile(user, address, creditcard, pay_id = None): user, address, creditcard).make_request() - - def _make_transaction(trans_cls, amount, user, pay_id, - order = None, trans_id = None, test = None): + order=None, trans_id=None, test=None): """ private function for handling transactions (since the data is effectively the same regardless of trans_cls) @@ -84,32 +110,33 @@ def _make_transaction(trans_cls, amount, user, pay_id, # lookup customer ID cust_id = CustomerID.get_id(user) # create a new transaction - trans = trans_cls(amount, cust_id, pay_id, trans_id = trans_id, - order = order) + trans = trans_cls(amount, cust_id, pay_id, trans_id=trans_id, + order=order) extra = {} # the optional test field makes the transaction a test, and will # make the response be the error code corresponding to int(test). if isinstance(test, int): - extra = dict(x_test_request = "TRUE", - x_card_num = test_card.ERRORCARD.cardNumber, - x_amount = test) + extra = dict(x_test_request="TRUE", + x_card_num=test_card.ERRORCARD.cardNumber, + x_amount=test) # using the transaction, generate a transaction request and make it - req = CreateCustomerProfileTransactionRequest(transaction = trans, - extraOptions = extra) + req = CreateCustomerProfileTransactionRequest(transaction=trans, + extraOptions=extra) return req.make_request() -def auth_transaction(amount, user, payid, thing, campaign, test = None): +@export +def auth_transaction(amount, user, payid, thing, campaign, test=None): # use negative pay_ids to identify freebies, coupons, or anything # that doesn't require a CC. if payid < 0: trans_id = -thing._id # update previous freebie transactions if we can try: - bid = Bid.one(thing_id = thing._id, - transaction = trans_id, - campaign = campaign) + bid = Bid.one(thing_id=thing._id, + transaction=trans_id, + campaign=campaign) bid.bid = amount bid.auth() except NotFound: @@ -117,10 +144,10 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None): return bid.transaction, "" elif int(payid) in PayID.get_ids(user): - order = Order(invoiceNumber = "T%dC%d" % (thing._id, campaign)) + order = Order(invoiceNumber="T%dC%d" % (thing._id, campaign)) success, res = _make_transaction(ProfileTransAuthOnly, amount, user, payid, - order = order, test = test) + order=order, test=test) if success: if test: return auth_transaction(amount, user, -1, thing, campaign, @@ -130,7 +157,7 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None): return res.trans_id, "" elif res is None: # we are in test mode! - return auth_transaction(amount, user, -1, thing, test = test) + return auth_transaction(amount, user, -1, thing, test=test) # duplicate transaction, which is bad, but not horrible. Log # the transaction id, creating a new bid if necessary. elif res.trans_id and (res.response_code, res.response_reason_code) == (3,11): @@ -143,24 +170,27 @@ def auth_transaction(amount, user, payid, thing, campaign, test = None): return res.trans_id, res.response_reason_text - -def void_transaction(user, trans_id, campaign, test = None): - bid = Bid.one(transaction = trans_id, campaign = campaign) +@export +def void_transaction(user, trans_id, campaign, test=None): + bid = Bid.one(transaction=trans_id, campaign=campaign) bid.void() if trans_id > 0: res = _make_transaction(ProfileTransVoid, - None, user, None, trans_id = trans_id, - test = test) + None, user, None, trans_id=trans_id, + test=test) return res +@export def is_charged_transaction(trans_id, campaign): if not trans_id: return False # trans_id == 0 means no bid - bid = Bid.one(transaction = trans_id, campaign = campaign) + bid = Bid.one(transaction=trans_id, campaign=campaign) return bid.is_charged() -def charge_transaction(user, trans_id, campaign, test = None): - bid = Bid.one(transaction = trans_id, campaign = campaign) + +@export +def charge_transaction(user, trans_id, campaign, test=None): + bid = Bid.one(transaction=trans_id, campaign=campaign) if not bid.is_charged(): bid.charged() if trans_id < 0: @@ -169,8 +199,8 @@ def charge_transaction(user, trans_id, campaign, test = None): elif bid.account_id == user._id: res = _make_transaction(ProfileTransPriorAuthCapture, bid.bid, user, - bid.pay_id, trans_id = trans_id, - test = test) + bid.pay_id, trans_id=trans_id, + test=test) return bool(res) # already charged diff --git a/r2/r2/tests/unit/lib/authorize/test_api.py b/r2/r2/tests/unit/lib/authorize/test_api.py new file mode 100755 index 000000000..55d940561 --- /dev/null +++ b/r2/r2/tests/unit/lib/authorize/test_api.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python + +import unittest + +class AuthorizeNetExceptionTest(unittest.TestCase): + + def test_exception_message(self): + from r2.lib.authorize.api import AuthorizeNetException + card_number = "1111222233334444" + expected = "...4444" + full_msg = "Wrong Card %s was given" + + exp = AuthorizeNetException(full_msg % (card_number)) + + self.assertNotEqual(str(exp), (full_msg % card_number)) + self.assertEqual(str(exp), (full_msg % expected)) + +class SimpleXMLObjectTest(unittest.TestCase): + + def setUp(self): + from r2.lib.authorize.api import SimpleXMLObject + self.basic_object = SimpleXMLObject(name="Test", + test="123", + ) + + def test_to_xml(self): + self.assertEqual(self.basic_object.toXML(), + "123Test", + "Unexpected XML produced") + + def test_simple_tag(self): + from r2.lib.authorize.api import SimpleXMLObject + xml_output = SimpleXMLObject.simple_tag("cat", "Jini", breed="calico", + demenor="evil", + ) + self.assertEqual(xml_output, + 'Jini') + + def test_from_xml(self): + from r2.lib.authorize.api import SimpleXMLObject + from BeautifulSoup import BeautifulStoneSoup + class TestXML(SimpleXMLObject): + _keys = ["color", "breed"] + + parsed = BeautifulStoneSoup("" + + "black" + + "mixed" + + "else" + + "") + constructed = TestXML.fromXML(parsed) + expected = SimpleXMLObject(color="black", + breed="mixed", + ) + self.assertEqual(constructed.toXML(), expected.toXML(), + "Constructed does not match expected") + + def test_address(self): + from r2.lib.authorize import Address + address = Address(firstName="Bob", + lastName="Smith", + company="Reddit Inc.", + address="123 Main St.", + city="San Francisco", + state="California", + zip="12345", + country="USA", + phoneNumber="415-555-1234", + faxNumber="415-555-4321", + customerPaymentProfileId="1234567890", + customerAddressId="2233", + ) + expected = ("Bob" + + "Smith" + + "Reddit Inc." + + "
123 Main St.
" + + "San Francisco" + + "California" + + "12345" + + "USA" + + "415-555-1234" + + "415-555-4321" + + "1234567890" + + "2233") + + self.assertEqual(address.toXML(), expected) + + def test_credit_card(self): + from r2.lib.authorize import CreditCard + card = CreditCard(cardNumber="1111222233334444", + expirationDate="11/22/33", + cardCode="123" + ) + expected = ("1111222233334444" + + "11/22/33" + + "123") + self.assertEqual(card.toXML(), expected) + + def test_payment_profile(self): + from r2.lib.authorize.api import PaymentProfile + profile = PaymentProfile(billTo="Joe", + paymentId="222", + card="1111222233334444", + validationMode="42", + ) + expected = ("Joe" + + "" + + "1111222233334444" + + "" + + "222" + + "42") + self.assertEqual(profile.toXML(), expected) + + def test_transation(self): + from r2.lib.authorize.api import Transaction + transaction = Transaction(amount="42.42", + profile_id="112233", + pay_id="1111", + trans_id="2222", + order="42", + ) + + expected = ("" + + "42.42" + + "112233" + + "1111" + + "2222" + + "42" + + "") + self.assertEqual(transaction.toXML(), expected) + +class ImportTest(unittest.TestCase): + + def test_importable(self): + #validator + from r2.lib.authorize import Address, CreditCard + #promotecontroller + from r2.lib.authorize import ( + get_account_info, + edit_profile, + PROFILE_LIMIT, + ) + #promote.py + from r2.lib.authorize import ( + auth_transaction, + charge_transaction, + is_charged_transaction, + void_transaction, + ) + + +def get_suite(): + suite = unittest.TestSuite() + suite.addTest(AuthorizeNetExceptionTest('test_exception_message')) + suite.addTest(SimpleXMLObjectTest('test_to_xml')) + suite.addTest(SimpleXMLObjectTest('test_simple_tag')) + suite.addTest(SimpleXMLObjectTest('test_from_xml')) + suite.addTest(SimpleXMLObjectTest('test_address')) + suite.addTest(SimpleXMLObjectTest('test_credit_card')) + suite.addTest(SimpleXMLObjectTest('test_payment_profile')) + suite.addTest(SimpleXMLObjectTest('test_transation')) + suite.addTest(ImportTest('test_importable')) + return suite + +def run_tests(): + unittest.TextTestRunner(verbosity=2).run(get_suite()) + +if __name__ == '__main__': + run_tests()