From 8dc7f77bae16219681bc6f33b95575a356182857 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 13:29:07 -0600 Subject: [PATCH] more tests for db, improvements to Trade and DB classes --- xcat/cli.py | 8 +++-- xcat/db.py | 32 +++++++++----------- xcat/tests/test_cli.py | 2 +- xcat/tests/unit/test_db.py | 60 +++++++++++++++++++++++++++++--------- xcat/tests/utils.py | 9 +++++- xcat/trades.py | 36 +++++++++++++++++++---- 6 files changed, 105 insertions(+), 42 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 989ac18..389ca69 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -7,6 +7,7 @@ from xcat.db import DB import xcat.userInput as userInput import xcat.utils as utils from xcat.protocol import Protocol +from xcat.trades import Trade def save_state(trade, tradeid): @@ -26,9 +27,11 @@ def checkSellStatus(tradeid): if status == 'init': userInput.authorize_fund_sell(trade) fund_tx = protocol.fund_sell_contract(trade) + print("Sent fund_tx", fund_tx) trade.sell.fund_tx = fund_tx save_state(trade, tradeid) + elif status == 'buyerFunded': secret = db.get_secret(tradeid) print("Retrieved secret to redeem funds for " @@ -45,10 +48,12 @@ def checkSellStatus(tradeid): save_state(trade, tradeid) # Remove from db? Or just from temporary file storage utils.cleanup(tradeid) + elif status == 'sellerFunded': print("Buyer has not yet funded the contract where you offered to " "buy {0}, please wait for them to complete " "their part.".format(trade.buy.currency)) + elif status == 'sellerRedeemed': print("You have already redeemed the p2sh on the second chain of " "this trade.") @@ -137,10 +142,9 @@ def checkBuyStatus(tradeid): # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): - db = DB() protocol = Protocol() trade = utils.x2s(hexstr) - trade = db.instantiate(trade) + trade = Trade(trade) protocol.import_addrs(trade) print(trade.toJSON()) save_state(trade, tradeid) diff --git a/xcat/db.py b/xcat/db.py index e1ea1cf..79bac22 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -16,36 +16,32 @@ class DB(): # Takes dict or obj, saves json str as bytes def create(self, trade, tradeid): - if type(trade) == dict: - trade = json.dumps(trade) - else: + if isinstance(trade, dict): + trade = json.dumps(trade, sort_keys=True, indent=4) + elif isinstance(trade, Trade): trade = trade.toJSON() + else: + raise ValueError('Expected dictionary or Trade object') self.db.put(utils.b(tradeid), utils.b(trade)) # Uses the funding txid as the key to save trade def createByFundtx(self, trade): - trade = trade.toJSON() - # # Save trade by initiating txid - jt = json.loads(trade) - txid = jt['sell']['fund_tx'] + if isinstance(trade, dict): + txid = trade['sell']['fund_tx'] + trade = json.dumps(trade, sort_keys=True, indent=4) + elif isinstance(trade, Trade): + txid = trade.sell.fund_tx + trade = trade.toJSON() + else: + raise ValueError('Expected dictionary or Trade object') self.db.put(utils.b(txid), utils.b(trade)) def get(self, tradeid): rawtrade = self.db.get(utils.b(tradeid)) tradestr = str(rawtrade, 'utf-8') - trade = self.instantiate(tradestr) + trade = Trade(fromJSON=tradestr) return trade - @staticmethod - def instantiate(trade): - if type(trade) == str: - tradestr = json.loads(trade) - trade = Trade( - buy=Contract(tradestr['buy']), - sell=Contract(tradestr['sell']), - commitment=tradestr['commitment']) - return trade - ############################################# ###### Preimages stored by tradeid ########## ############################################# diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 36a7bd3..5864cee 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,8 +1,8 @@ import unittest import xcat.cli as cli +import xcat.tests.utils as testutils from xcat.db import DB from xcat.protocol import Protocol -import xcat.tests.utils as testutils from xcat.trades import Trade # , Contract diff --git a/xcat/tests/unit/test_db.py b/xcat/tests/unit/test_db.py index 1e4281b..b71d3e4 100644 --- a/xcat/tests/unit/test_db.py +++ b/xcat/tests/unit/test_db.py @@ -1,5 +1,6 @@ import unittest import unittest.mock as mock +import json import xcat.db as db import xcat.tests.utils as utils @@ -14,32 +15,63 @@ class TestDB(unittest.TestCase): self.assertIsInstance(self.db.db, mock.Mock) self.assertIsInstance(self.db.preimageDB, mock.Mock) - @mock.patch('xcat.db.json') - def test_create_with_dict(self, mock_json): + def test_create_with_dict(self): test_id = 'test trade id' - trade_string = 'trade string' - mock_json.dumps.return_value = trade_string - test_trade = utils.test_trade - self.db.create(test_trade, test_id) + self.db.create(utils.test_trade_dict, test_id) - mock_json.dumps.assert_called_with(test_trade) self.db.db.put.assert_called_with( str.encode(test_id), - str.encode(trade_string)) + str.encode(str(utils.test_trade))) def test_create_with_trade(self): - pass + test_id = 'test trade id' - def test_createByFundtx(self): - pass + self.db.create(utils.test_trade, test_id) + + self.db.db.put.assert_called_with( + str.encode(test_id), + str.encode(json.dumps(utils.test_trade_dict, + sort_keys=True, + indent=4))) + + def test_create_with_error(self): + with self.assertRaises(ValueError) as context: + self.db.create('this is not valid input', 'trade_id') + + self.assertTrue( + 'Expected dictionary or Trade object' + in str(context.exception)) + + def test_createByFundtx_with_dict(self): + self.db.createByFundtx(utils.test_trade_dict) + + self.db.db.put.assert_called_with( + str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d' + '59511566a2fdb'), + str.encode(str(utils.test_trade))) + + def test_createByFundtx_with_trade(self): + self.db.createByFundtx(utils.test_trade) + + self.db.db.put.assert_called_with( + str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d' + '59511566a2fdb'), + str.encode(json.dumps(utils.test_trade_dict, + sort_keys=True, + indent=4))) + + def test_createByFundtx_with_error(self): + with self.assertRaises(ValueError) as context: + self.db.createByFundtx('this is not valid input') + + self.assertTrue( + 'Expected dictionary or Trade object' + in str(context.exception)) def test_get(self): pass - def test_instantiate(self): - pass - def test_save_secret(self): pass diff --git a/xcat/tests/utils.py b/xcat/tests/utils.py index 9f556e9..b6865e2 100644 --- a/xcat/tests/utils.py +++ b/xcat/tests/utils.py @@ -1,6 +1,7 @@ from xcat.db import DB +from xcat.trades import Contract, Trade -test_trade = { +test_trade_dict = { "sell": { "amount": 3.5, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", @@ -21,6 +22,12 @@ test_trade = { "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} +test_sell_contract = Contract(test_trade_dict['sell']) +test_buy_contract = Contract(test_trade_dict['buy']) +test_trade = Trade(sell=test_sell_contract, + buy=test_buy_contract, + commitment=test_trade_dict['commitment']) + def mktrade(): db = DB() diff --git a/xcat/trades.py b/xcat/trades.py index 40a2ab2..2857d67 100644 --- a/xcat/trades.py +++ b/xcat/trades.py @@ -1,19 +1,43 @@ import json -class Trade(object): - def __init__(self, sell=None, buy=None, commitment=None): +class Trade(): + def __init__(self, sell=None, buy=None, commitment=None, + fromJSON=None, fromDict=None): '''Create a new trade with buy and sell contracts across two chains''' - self.sell = sell - self.buy = buy - self.commitment = commitment + + if fromJSON is not None and fromDict is None: + if isinstance(fromJSON, str): + fromDict = json.loads(fromJSON) + else: + raise ValueError('Expected json string') + if fromDict is not None: + self.sell = Contract(fromDict['sell']) + self.buy = Contract(fromDict['buy']) + self.commitment = fromDict['commitment'] + else: + self.sell = sell + self.buy = buy + self.commitment = commitment def toJSON(self): return json.dumps( self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + def __str__(self): + return self.toJSON() -class Contract(object): + def __repr__(self): + return 'Trade:\n{0} {1} from {2}\nfor\n{3} {4} from {5}'.format( + self.sell.amount, + self.sell.currency, + self.sell.initiator, + self.buy.amount, + self.buy.currency, + self.buy.initiator) + + +class Contract(): def __init__(self, data): allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', 'fund_tx', 'redeem_tx', 'secret', 'redeemScript',