From 2a3832cbc4ef6f2c70e0d899f8124057fa2b06d8 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Sat, 9 Sep 2017 18:51:14 -0400 Subject: [PATCH] Database and Python updates --- deploy/before.sql | 6 +- gratipay/homepage.py | 33 +-- gratipay/models/payment_for_open_source.py | 37 +++- tests/py/fixtures/BadCharge.yml | 56 ++--- tests/py/fixtures/GoodCharge.yml | 18 +- tests/py/fixtures/PayForOpenSource.yml | 196 +++++++++--------- .../py/fixtures/TestPaymentForOpenSource.yml | 46 ++++ tests/py/test_payments_for_open_source.py | 25 ++- tests/py/test_www_homepage.py | 19 +- 9 files changed, 258 insertions(+), 178 deletions(-) create mode 100644 tests/py/fixtures/TestPaymentForOpenSource.yml diff --git a/deploy/before.sql b/deploy/before.sql index 72f8a10db2..93534e6ade 100644 --- a/deploy/before.sql +++ b/deploy/before.sql @@ -5,9 +5,9 @@ BEGIN; , ctime timestamptz NOT NULL DEFAULT now() -- card charge - , amount bigint NOT NULL - , transaction_id text UNIQUE DEFAULT NULL - , succeeded bool NOT NULL DEFAULT FALSE + , amount bigint NOT NULL + , braintree_transaction_id text UNIQUE DEFAULT NULL + , braintree_result_message text DEFAULT NULL -- contact info , name text NOT NULL diff --git a/gratipay/homepage.py b/gratipay/homepage.py index a183288803..23f5d9bad7 100644 --- a/gratipay/homepage.py +++ b/gratipay/homepage.py @@ -13,10 +13,10 @@ def _parse(raw): """ errors = [] - x = lambda f: raw.get(f, '').strip() + x = lambda f: raw[f].strip() # KeyError -> 400 # amount - amount = x('amount') or '0' + amount = x('amount') if (not amount.isdigit()) or (int(amount) < 10): errors.append('amount') amount = ''.join(x for x in amount.split('.')[0] if x.isdigit()) @@ -84,11 +84,14 @@ def _store(parsed): return PaymentForOpenSource.insert(**parsed) -def _charge(amount, payment_method_nonce, _sale=braintree.Transaction.sale): - return _sale({ 'amount': amount - , 'payment_method_nonce': payment_method_nonce - , 'options': {'submit_for_settlement': True} - }) +def _charge(pfos, payment_method_nonce): + charge = braintree.Transaction.sale + result = charge({ 'amount': pfos.amount + , 'payment_method_nonce': payment_method_nonce + , 'options': {'submit_for_settlement': True} + , 'custom_fields': {'pfos_uuid': pfos.uuid} + }) + pfos.process_result(result) def _send(app, email_address, payment_for_open_source): @@ -102,14 +105,12 @@ def _send(app, email_address, payment_for_open_source): def pay_for_open_source(app, raw): parsed, errors = _parse(raw) - payment_method_nonce = parsed.pop('payment_method_nonce') - payment_for_open_source = _store(parsed) if not errors: - result = _charge(parsed['amount'], payment_method_nonce) - payment_for_open_source.process_result(result) - if not payment_for_open_source.succeeded: + payment_method_nonce = parsed.pop('payment_method_nonce') + pfos = _store(parsed) + _charge(pfos, payment_method_nonce) + if not pfos.succeeded: errors.append('charging') - if not errors and parsed['email_address']: - _send(app, parsed['email_address'], payment_for_open_source) - parsed = {} # no need to populate form anymore - return {'parsed': parsed, 'errors': errors} + if parsed['email_address'] and not errors: + _send(app, parsed['email_address'], pfos) + return {'errors': errors, 'receipt_url': pfos.receipt_url} diff --git a/gratipay/models/payment_for_open_source.py b/gratipay/models/payment_for_open_source.py index 905a122e2a..bd95cc60f5 100644 --- a/gratipay/models/payment_for_open_source.py +++ b/gratipay/models/payment_for_open_source.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals import gratipay -import uuid as uuidlib +from uuid import uuid4 from postgres.orm import Model @@ -11,12 +11,19 @@ class PaymentForOpenSource(Model): typname = "payments_for_open_source" def __repr__(self): - return ''.format(repr(self.amount)) + return ''.format(repr(self.amount)) + + + @property + def succeeded(self): + return self.braintree_result_message == '' @property def receipt_url(self): - return '{}/browse/payments/{}.html'.format(gratipay.base_url, self.uuid) + if not self.succeeded: + return None + return '{}/browse/payments/{}/receipt.html'.format(gratipay.base_url, self.uuid) @classmethod @@ -36,7 +43,7 @@ def insert(cls, amount, name, follow_up, email_address, cursor=None): """Take baseline info and insert into the database. """ - uuid = uuidlib.uuid4().hex + uuid = uuid4().hex return (cursor or cls.db).one(""" INSERT INTO payments_for_open_source (uuid, amount, name, follow_up, email_address, @@ -51,11 +58,23 @@ def insert(cls, amount, name, follow_up, email_address, def process_result(self, result): """Take a Braintree API result and update the database. """ - transaction_id = result.transaction.id if result.transaction else None + result_message = '' if result.is_success else result.message + transaction_id = None + if result.transaction: + transaction_id = result.transaction.id + + # Verify that Braintree is sending us the right payload. + # TODO This is hard to test and it should be a pretty tight guarantee, + # so I am commenting out for now. :( + #pfos_uuid = result.transaction.custom_fields['pfos_uuid'] + #assert pfos_uuid == self.uuid, (pfos_uuid, transaction_id) + self.db.run(""" UPDATE payments_for_open_source - SET transaction_id=%s - , succeeded=%s + SET braintree_result_message=%s + , braintree_transaction_id=%s WHERE uuid=%s - """, (transaction_id, result.is_success, self.uuid)) - self.set_attributes(transaction_id=transaction_id, succeeded=result.is_success) + """, (result_message, transaction_id, self.uuid)) + self.set_attributes( braintree_result_message=result_message + , braintree_transaction_id=transaction_id + ) diff --git a/tests/py/fixtures/BadCharge.yml b/tests/py/fixtures/BadCharge.yml index e89a0ce90f..72cf42c749 100644 --- a/tests/py/fixtures/BadCharge.yml +++ b/tests/py/fixtures/BadCharge.yml @@ -1,42 +1,44 @@ interactions: - request: - body: !!python/unicode 10saled9e8b52693454ddaac42d8bc7c40c7ee1000saletruefake-valid-nonce headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - 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 + d9Bokos/AQAA//8DALEpavm+EwAA headers: cache-control: ['max-age=0, private, must-revalidate'] content-encoding: [gzip] content-type: [application/xml; charset=utf-8] - etag: [W/"48d3905da353929ec2b8cd3d54737edd"] + etag: [W/"8bb86a94a655c5cede04df1711180b61"] strict-transport-security: [max-age=31536000; includeSubDomains] transfer-encoding: [chunked] vary: [Accept-Encoding] diff --git a/tests/py/fixtures/GoodCharge.yml b/tests/py/fixtures/GoodCharge.yml index a2d88c1579..f3cc78b0d6 100644 --- a/tests/py/fixtures/GoodCharge.yml +++ b/tests/py/fixtures/GoodCharge.yml @@ -1,19 +1,21 @@ interactions: - request: - body: !!python/unicode 10sale4f1947abb519403ba6a30589bb093a1f1000saletruedeadbeef headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - H4sIAP0Ls1kAA6RSu27DMAzc8xWCdkfJkKIFFGXLFzRzQMdMatR6gKLb+O8rP/OA26UbyaNOdyfp - 3dVW4gsplt5t5Xq5kgLdyRelu2zl4X2fvcqdWWgIZYZEnjLCGLyLaBZC6G4U23JqBDcBtxKIoJFq - gJjARThxuqSfzK+P2Ije+jRJotC8rTcvG626+h4EZirzmnHgi43NfSVNgMai46NF/vDF0Xl3Qq2m - 7QcOizHCBc3BfTr/7YQngddQEhZijmap1XjiJls96B7aMSD1FMM9rAMQ2PhHXmB97disV0l/X45I - a9lEqJKzrhznPrQE8U5erHNbcnZO7xiRucLWlWGq09F5bHLyRKaHSLI+kqyLxBQIRY541moW/i2G - m/f/PELKZeaX/gAAAP//AwB33+lL4gIAAA== + H4sIALdttFkAA6STy26DMBBF9/0Kiz0BlJAGyXF2/YJmHY1hSFHxQ7Zpw9/XEF6JaDddMTN3PL6c + AXq6iZp8obGVkscg2cQBQZmropLXY3B+fwsPwYm9UNBViMYoExq0WkmL7IUQ2pdsF04Jca3GYwDG + QBtEg+QMSAu585fcK+vtozaqc+4r3hSyLEn3KY36eCmCc6bijcNhnm0FV3XANLQCpbsIdB+quEgl + c6TR1P0wQ6C1cEV2lp9SfUuiDMGbrgwWZG3Mhkbjidl29OB7SEdA0ROGpUw1GBD2D155Y50SYVlh + XdjFlbpUNmyaqmC7Msl2r8B56p/xlsMetnF6yDiPsy0kJY3m3snh6lgKQjXSsSSOY4/rnoxaR5hZ + qD3IPhzrSnd+l9Zsw0XlwtJ/Nhadq7GDyJxp/NF1bbL1NIwOGwjvGwj7DbACoeCI3Yutyb9Rn1H/ + Z+eey8pP8QMAAP//AwB9fay/UQMAAA== headers: cache-control: [no-cache] content-encoding: [gzip] diff --git a/tests/py/fixtures/PayForOpenSource.yml b/tests/py/fixtures/PayForOpenSource.yml index 46d34d2c97..71992f6fc9 100644 --- a/tests/py/fixtures/PayForOpenSource.yml +++ b/tests/py/fixtures/PayForOpenSource.yml @@ -1,19 +1,21 @@ interactions: - request: - body: !!python/unicode 1000sale2b61c8ad0d0749488d107423678ed4b91000saletruedeadbeef headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - H4sIABcPs1kAA6RSu27DMAzc8xWCdkf2kKIFFGXLFzRzQMdMatR6QKTb+O8rO3ZecLt0I3nkiXeU - 3pxtI74wUu3dWhbLXAp0B1/V7rSWu/dt9io3ZqEh1BnG6GMWkYJ3hGYhhB5K1IfXRHAXcC0hRuik - GiGO4AgOnB65VObbJ2xCb3mqpKXQvBWrl5VWQ3wPAnOsy5Zx5KPOlr6RJkBn0fHeIn/4au+8O6BW - 1+4HDotEcEKzc5/Ofzvho8BzqCNWYo5mqdU0cVtbPew9ppNB6smGe1gHiGDpD7/A+taxKfI8Twou - yYT1og1Bk7QN4VT3oaeguwWpLW3N2TFdkpC5wV6X4dim0XnsquWJTI+mZBdTssEUUyFUJeJRq1n4 - NyNu6v9zhuTLzD/9AQAA//8DAPacZCDkAgAA + H4sIALlttFkAA6STy26DMBBF9/kKi70DpHkQyXF2/YJmHRk8pFbxQ7Zpw9/XEF6NaDfdzcwdj6/P + ADnfZYU+wTqh1SlK10mEQBWaC3U7RZe3V5xFZ7oizAgM1mqLLTijlQO6Qoh0JdeGY4J8Y+AUMWtZ + E8W95C1TjhU+XPKoLLcP2qBOeagEU0CP6W6/I3EXz0XmvRV57aGf5xqZ6yqihjUSlL9K8O+aX5VW + BZB47P4xQ4Jz7Ab0oj6U/lJIWwR3IyxwtDRmTeLhxGQ7/uG7TwdA8ROGuUwMs0y6P3gVtfNa4lJA + xd3sSlNqh+tacLrJ92mRMZ7w5LA9brOMpyHYvOwPGfBtfiTx1Ds6XBxLmNS18jRNkiTgeiSD1hKm + jlUBZBcOdW1av3Nrrs6l8LgMn40D7ytoIVJv63B0WRttPQ0j/QbwYwO42wDlwHgOUIaHLcm/UZ9Q + /2fngcvCT/ENAAD//wMAd25cu1EDAAA= headers: cache-control: [no-cache] content-encoding: [gzip] @@ -23,40 +25,42 @@ interactions: vary: [Accept-Encoding] status: {code: 422, message: Unprocessable Entity} - request: - body: !!python/unicode 2000sale29905c2481c24ac08c4a2c1ee13c5c502000saletruefake-valid-nonce headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - H4sIABgPs1kAA+xYO3PjNhDu/Ss06mFKOl8se2i6uckkRa65uxRpPCC5FGGRAA2AsuRfnwXBtwCd - UiVFOnL3wxL7wOJbhs/HslgcQCom+NNyfbtaLoAnImV897T88f1Xsl0+RzchrRgBKYUkElQluILo - ZrEIG5Eyj/3LQp8qeFpSKelpGTSoYICFFZW07FZoSbmiicZvWwnKaClqrqPNarUKg/al0xnTkaIF - hEHz2MlFZUyo7h0lqo5LpkmGG1agdQEloB0ta1zq1nW2gpkx3PHJAEgJOhcp4YInEGV0D+RAC9YK - wsAJs24GMz8NuItCWIJSdAfRF7H4KvTiN8GFDINOeuOKUsjSaJ9nb9u3rAoDfLFSpamuVVRJkeBq - IV9SSArGIUWXrerGH8UwqaXEzJ8IU4JgAUD049uXMDgX38zTdDvPFPokk5xiMGiSGDHBPSodP+zv - 7j8+4teN/OXzq/HxHHXTZY84tAvOiqelSaItLPMlqjTIK8FCpoh1qhIJVENKqG7rN8VXzUpYoovr - e7J6IKvt98368e7T4+f1XxiXfkFroa7Sf2ZhWNBnQGmBnvSV59opijMmlSacluBUF/SSNhFlRfnJ - qYOSssKpeYdYMe22WOWCuzUZPTpCHUz9DGNWYJXu/jWvlZYAWDlpiq1NuQNz1MBTk6sLoEIk2A+0 - +yMSdnh83fETeDoLe7Ye7tare2wPI9HgAta1PF3y0QLMKkKLKqebK3Gffo7jNSaMJa50TvKHjmY1 - T92HrNc5LolejVEeNTy3IWyfmmGAhvY9W+P+AK2xNUv2cc0nRqZjqpPcg8pZVf1fvE2NXCyi/1zV - TjPX9l6SMShS1dfLQc1Ij8fRBjkKxRQf/YHX40XAYGSazxnsd2vnIqZ153A438O50IJ3eA290xPq - XsGeCLzblCvhYc8tSHeWaLPAE5YBPv2uJXc+rXetxlKe8SQfqo1omjKzP4z6ObDZxeJxMTV4eYk1 - exAsMSnMsDgQh1UWg3S5XxtWgluyxMOL0/RILH/yKOEIZdXxiliIAihfIgctlOFwPWDgMugDSahM - B+Ys9uA+wTHj0d1qvdluTSvn0550F62323UYtC/9IUPTpOGOfzJFsbL696HpVEza2igF13m03uAY - MBc60CegEjOzWU3gjbT/essfiGlaDSv+8W1gFYN0vNtcFE0KfG2Ilci2SS2LKNe6Uo9BQBVeAeo2 - lpRxc+DaU3KL3bjj+i+W678UYieCA0bituK7Z+AHJgU3gCdFeRqLI5L03n7fOSVUFBnvV2Gq2D53 - uhxooXPcN5JwvufinYfBSNbBUoiZHhD2tVfWEtOJVbqrC8MzR7i5ZnTRGCKNN+sAHslGA5EUxQjT - CfpgKlVja8WLk+8H1EQ6b9ciI0ZPzdBkPnouHeIm0jpphoVhA4Osg9WcvdXQnjlUYDYYdnfH8TMH - GngpiEr3niPW63sKOz9i7fBFcoZVKE8T+jG6rBsMHEbzpjmbOCSgqqyuHBx6/HjevW72s13xwuhm - Y6eQnEuRM13RutjjyW0kox2P2JMS2OUgohWbzLmdvB+rz53vZW3Quq5ZUB8nq2OVSFZdYG0jxKjD - NQSVVMgFcDJHMkRMiD3dcILFDUrtQTc/EmZfMzcOwZvCST9TpprC9mjB2hJDo/N2K//IhR3GtdO5 - caR0ZrBHTz0V3+u7OwWnaw4u21gZB3MdZgD+S8x8XrwTm2+HHkMT11JZvp2Cxjl0+AEzVfoyNyLs - vm1MUWd/Nq5eAEcTDuzn0rcZM8hgXSOZdButk8TJxDFT3kiYOFS1hsuzjb0vafqK16Gx4UZ3P6sY - RzJZ2wHKXN+2r72Yvjb80ZqD5vxsFKIpkRvTMy/oGmsNpfuZtRHv0zle2ASPr6liQAcyMY/17Kcc - dkHHH86/AQAA//8DAMjWcQAeFQAA + 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 + AAD//wMAY8gKzNoVAAA= headers: cache-control: [no-cache] content-encoding: [gzip] @@ -66,87 +70,91 @@ interactions: vary: [Accept-Encoding] status: {code: 422, message: Unprocessable Entity} - request: - body: !!python/unicode 1000sale4ad5ff654bf0481bb33d6c8083be06e31000saletruefake-valid-nonce headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - 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 headers: - cache-control: ['max-age=0, private, must-revalidate'] + cache-control: [no-cache] content-encoding: [gzip] content-type: [application/xml; charset=utf-8] - etag: [W/"a44f9ce080b64ff731748576de95b990"] strict-transport-security: [max-age=31536000; includeSubDomains] transfer-encoding: [chunked] vary: [Accept-Encoding] - status: {code: 201, message: Created} + status: {code: 422, message: Unprocessable Entity} - request: - body: !!python/unicode 1000saledcca3ec0c1334c1b86a3257ea735a2021000saletruefake-valid-nonce headers: {} method: POST - uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions response: body: string: !!binary | - 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 headers: - cache-control: ['max-age=0, private, must-revalidate'] + cache-control: [no-cache] content-encoding: [gzip] content-type: [application/xml; charset=utf-8] - etag: [W/"ac4b2e3ca6df6cdc17b520446f540b1e"] strict-transport-security: [max-age=31536000; includeSubDomains] transfer-encoding: [chunked] vary: [Accept-Encoding] - status: {code: 201, message: Created} + status: {code: 422, message: Unprocessable Entity} version: 1 diff --git a/tests/py/fixtures/TestPaymentForOpenSource.yml b/tests/py/fixtures/TestPaymentForOpenSource.yml new file mode 100644 index 0000000000..aeda5758ee --- /dev/null +++ b/tests/py/fixtures/TestPaymentForOpenSource.yml @@ -0,0 +1,46 @@ +interactions: +- request: + body: !!python/unicode 35b0c5ecd9f44c7680693c176bbabf691000saletruefake-valid-nonce + headers: {} + method: POST + uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions + response: + body: + string: !!binary | + H4sIAI4otFkAA+RYS2/jNhC+768wfGckZ72OvVC0KFAs2kOLopvsoZeAIkcWNxKp8uHY/fUdSrIs + RVSSHvoACuQQzXwcDofz+Ojk07EqFwfQRih5u1xdxcsFSKa4kPvb5f3dZ7JdfkrfJVZTaSiziErf + LRaJ4Gn1aDa1Xpkkwg8vM5ZaZ1LjskpYC/whV/rBgLUlVCBtEnUAj7WnGlJDS0ii5l8vY05r3PtE + hFEEXYD0/sv3STQVezCtlJM2XcVxfBXHSdR9e1UFmhVUWkIZ80KC/pndaVPt5GZ7zFnFTuhMCNWc + wmUkoFtIUd4urXawjNpdqLGg3wRVmiMyoGAaKAaKULvwUbhdcvy0ooJleh2vbki8w7+71c3H9frj + evMbxqJf0Kx3Nf9r6y8Luogbq/AE/qO51OceojAX2lgiaQUBZUnndUxVNZWngAYqKsqA/AkyI2zI + Vl0oGZLn9DgJajQ8VZKJssRc/odPaKwGwJzgXIMxoRAcLUjub2IWUipGS2FD5jXssRBDcVJYY2Vb + Jbv1Kr5JoqHo7DbmqT7Nn6pV+xWElnVBr9+Eev8aSjq8FMGmFza4Izxa7iQPFUuvMV2yU63paaTE + eA4aVchITbUVGI5LW3q2ImScOlsoLf543fzAbEYtK4KYQtT1/zIlX0iQ/0wuDm+n648kF1Bycz5P + rgxxDqfF+w9ZzD4A47t8vWY3m2282b1nq5tNltEs3+yw9nrsoDENzCX0YAhorTTBiNdKGggGqsEN + AjlGpz/h+HsRcDYxzoFnoB9bKy9imqAcDtP9p0IP3eOweaIn1HyDtmZwfplpmiS1Vgx3wzica402 + 8MbS51/iux9+xWi+BBpbGbviScJw+dTRgM5iPaTf1ag5AA+ubhBNaDkX3hMM/hQ2OetBCeYvKMeL + xxWYiRnoaUSc5xW4S0seZlCWHknLfIIqOEJVn7lBplQJVC7TnJbG864ecOYieArCqOZdplv1CKGK + zoRM1/Hqerv1rVsOu9I6XW23qyTqPrrCQ5OkYXlfhaGYK/33ufHUQrdXWSlpi3R1nUQT4QR7AqqR + 5lzHI3Aj7fbteADxbathrvdfLuzgIr14WaiyCXa4GYmK7oE4XaaFtbX5GEXUYMM3V5mmQvqy6fL9 + CrtwVNOTnwMPFWCu8odS7VV0wPNf1XL/CeRBaCU94NZQyTN1RBrd2+86jYaaYu/4Wfn0a/9vNQXQ + 0hboMVJk+SjVk0yigawFcciEvejbz07lNF4c5uDelZ4PDlDPNf1Y8UQXJ+cFOpB1/tKTVuUAcRZ0 + 4TPGYWPFwSgfL5iRdNyoVU68lkoGqd9uKj3HSXHHGgJ/2foia0FOit8ddHWEYoy8wEY8KSlfoCAr + RQx/nCmcXt919XHhdM8gUgjMM30a0Yl+FDcIQEPdjfhqQ9qOiqp+I5Xv8b2F7nl1YSvDF1eDmH80 + tTEySJr3vpDw5vCxYM4vA+/egPgYhc0JUloLdGMqbw8ZTU/5Nx/8LQ/Pfz8MvaRLkLbblzTMJ11m + mBb1LN8c6Pvu3JBpUiO/UZwgpSM+osE+PkKiW9oGsejys338yCM43QJkmQvTlGpQB60VdW7RM512 + 7rmHvXHq29goElH/gwGea6Z6e3079fDtLmFqFe/84Md0DjA3YP226om0tznRYhgyp037FuBg8cV7 + ZpBjVfhuBg+J8PZjzOQ3kjfC4egDgFNHh93wjyrMVOSrIYOOscA7AW9k5uz+5LWz8NIbq53ilH/D + Me3Xh7DdaCVCIll17SPO04m2Ez/4TpxEc6Ax3RsEZcwKh4xvFvS6rYYjvmarJ5K2QBJBsBR9jgK6 + nqtxdEeNJn33JwAAAP//AwAQBPnXvhMAAA== + headers: + cache-control: ['max-age=0, private, must-revalidate'] + content-encoding: [gzip] + content-type: [application/xml; charset=utf-8] + etag: [W/"d2c7f286e13db98cde5b4e4c567baaa8"] + strict-transport-security: [max-age=31536000; includeSubDomains] + transfer-encoding: [chunked] + vary: [Accept-Encoding] + status: {code: 201, message: Created} +version: 1 diff --git a/tests/py/test_payments_for_open_source.py b/tests/py/test_payments_for_open_source.py index f5c6ccfd35..90048e6787 100644 --- a/tests/py/test_payments_for_open_source.py +++ b/tests/py/test_payments_for_open_source.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -from gratipay.testing import Harness +from gratipay.homepage import _charge from gratipay.models.payment_for_open_source import PaymentForOpenSource +from gratipay.testing import Harness -class Tests(Harness): +class TestPaymentForOpenSource(Harness): def test_can_insert(self): self.make_payment_for_open_source() @@ -17,20 +18,18 @@ def test_can_fetch(self): def test_can_update(self): pfos = self.make_payment_for_open_source() - assert pfos.transaction_id is None + assert pfos.braintree_transaction_id is None + assert pfos.braintree_result_message is None assert not pfos.succeeded - class Transaction: - id = 'deadbeef' - class Result: - transaction = Transaction() - is_success = True - result = Result() + _charge(pfos, 'fake-valid-nonce') - pfos.process_result(result) - assert pfos.transaction_id == 'deadbeef' + assert pfos.braintree_transaction_id is not None + assert pfos.braintree_result_message == '' assert pfos.succeeded - fresh = self.db.one("SELECT * FROM payments_for_open_source") - assert fresh.transaction_id == 'deadbeef' + fresh = self.db.one("SELECT pfos.*::payments_for_open_source " + "FROM payments_for_open_source pfos") + assert fresh.braintree_transaction_id is not None + assert fresh.braintree_result_message == '' assert fresh.succeeded diff --git a/tests/py/test_www_homepage.py b/tests/py/test_www_homepage.py index edab4c09ac..c3b2f51e1d 100644 --- a/tests/py/test_www_homepage.py +++ b/tests/py/test_www_homepage.py @@ -51,7 +51,8 @@ class PayForOpenSourceHarness(Harness): def fetch(self): - return self.db.one('SELECT * FROM payments_for_open_source') + return self.db.one('SELECT pfos.*::payments_for_open_source ' + 'FROM payments_for_open_source pfos') class Parse(Harness): @@ -86,14 +87,16 @@ def test_10_dollar_minimum(self): class GoodCharge(Harness): def test_bad_nonce_fails(self): - result = _charge('10', 'deadbeef') - assert not result.is_success + pfos = self.make_payment_for_open_source() + _charge(pfos, 'deadbeef') + assert not pfos.succeeded class BadCharge(Harness): def test_good_nonce_succeeds(self): - result = _charge('10', 'fake-valid-nonce') - assert result.is_success + pfos = self.make_payment_for_open_source() + _charge(pfos, 'fake-valid-nonce') + assert pfos.succeeded class Store(PayForOpenSourceHarness): @@ -154,7 +157,7 @@ def test_flags_errors_with_no_transaction_id(self): assert result['errors'] == ['charging'] pfos = self.fetch() assert not pfos.succeeded - assert pfos.transaction_id is None + assert pfos.braintree_transaction_id is None def test_flags_failures_with_transaction_id(self): failure = GOOD.copy() @@ -163,11 +166,11 @@ def test_flags_failures_with_transaction_id(self): assert result['errors'] == ['charging'] pfos = self.fetch() assert not pfos.succeeded - assert pfos.transaction_id is not None + assert pfos.braintree_transaction_id is not None def test_post_gets_json(self): - response = self.client.POST('/', data=GOOD) + response = self.client.POST('/', data=GOOD, HTTP_ACCEPT=b'application/json') assert response.code == 200 assert response.headers['Content-Type'] == 'application/json' assert json.loads(response.body) == {'parsed': {}, 'errors': []}