Files
archy/docker/justworks/test_payments.py
T

77 lines
4.4 KiB
Python
Raw Normal View History

import unittest
import http.client
import threading
from http.server import ThreadingHTTPServer
import server
from unittest.mock import patch
from urllib.error import HTTPError
import payments
BODY = {'siteId':'bakery', 'address':'mywallet@minibits.cash', 'credentials':{'ownerIdentifier':'owner', 'ownerPassword':'fake-test-password'}}
def record(address='mywallet@minibits.cash'):
return {'id':'bakery', 'nsite':{'deploymentStatus':'published'}, 'identity':{'profile':{'lud16':address}}}
class PaymentTests(unittest.TestCase):
def test_saves_only_address_and_checks_published_destination(self):
replies = [{'nsite':{'id':'bakery'}}, {}, {'artifact':{'verified':True}, 'nsite':record()}, {'nsite':record()}]
with patch('payments.request_core', side_effect=replies) as call:
result = payments.connect_payment(BODY)
self.assertTrue(result['verified'])
update = call.call_args_list[1]
self.assertEqual(update.args[:2], ('PATCH','/api/nsites/bakery'))
self.assertEqual(set(update.args[2]), {'payment','ownerIdentifier','ownerPassword'})
self.assertEqual(update.args[2]['payment'], BODY['address'])
self.assertEqual(call.call_args_list[-1].args, ('GET','/api/nsites/bakery'))
self.assertNotIn('password', str(result))
def test_rejected_owner_never_changes_or_publishes(self):
with patch('payments.request_core', side_effect=HTTPError('https://justworks.cash',401,'unauthorized',{},None)) as call:
with self.assertRaises(HTTPError): payments.connect_payment(BODY)
self.assertEqual(call.call_count,1)
def test_wrong_website_owner_never_changes(self):
with patch('payments.request_core', return_value={'nsite':{'id':'other'}}) as call:
with self.assertRaises(ValueError): payments.connect_payment(BODY)
self.assertEqual(call.call_count,1)
def test_partial_publish_and_wrong_destination_never_succeed(self):
for deploy, public in [({}, {'nsite':record()}),
({'artifact':{'verified':True},'nsite':record('other@minibits.cash')},{'nsite':record()}),
({'artifact':{'verified':True},'nsite':record()},{'nsite':record('other@minibits.cash')}),
(OSError('timeout'), {})]:
with self.subTest(deploy=deploy), patch('payments.request_core', side_effect=[{'nsite':{'id':'bakery'}},{},deploy,public]):
with self.assertRaises(payments.PublishUnverified): payments.connect_payment(BODY)
def test_invalid_input_never_reaches_upstream(self):
for body in [None, {}, {**BODY,'siteId':'../admin'}, {**BODY,'address':'someone@evil.test'}, {**BODY,'credentials':{}}, {**BODY,'credentials':{'ownerKey':['invalid']}}]:
with self.subTest(body=body), patch('payments.request_core') as call:
with self.assertRaises(ValueError): payments.connect_payment(body)
call.assert_not_called()
def test_concurrent_updates_are_rejected_without_writes(self):
with payments.LOCK, patch('payments.request_core') as call:
with self.assertRaises(ValueError): payments.connect_payment(BODY)
call.assert_not_called()
class PaymentHttpTests(unittest.TestCase):
def test_post_requires_exact_origin_json_and_bounded_body(self):
httpd = ThreadingHTTPServer(('127.0.0.1', 0), server.Handler)
worker = threading.Thread(target=httpd.serve_forever, daemon=True); worker.start()
host = f'127.0.0.1:{httpd.server_port}'
try:
with patch('server.connect_payment', return_value={'verified':True}) as connect:
for origin, content_type, body, expected in [
('https://evil.test','application/json','{}',403),
('','application/json','{}',403),
('http://'+host,'text/plain','{}',415),
('http://'+host,'application/json','x'*8193,413),
('http://'+host,'application/json','{}',200)]:
client = http.client.HTTPConnection(host)
client.request('POST','/api/payment-connect',body,{'Origin':origin,'Content-Type':content_type})
response=client.getresponse(); self.assertEqual(response.status,expected); response.read(); client.close()
self.assertEqual(connect.call_count,1)
finally:
httpd.shutdown(); httpd.server_close(); worker.join()
if __name__ == '__main__': unittest.main()