diff --git a/tests/unit/crypto/test_bip32.py b/tests/unit/crypto/test_bip32.py index fadbe96f1..562b8e718 100644 --- a/tests/unit/crypto/test_bip32.py +++ b/tests/unit/crypto/test_bip32.py @@ -1,10 +1,10 @@ from unittest import TestCase from binascii import unhexlify, hexlify -from lbry.blockchain.ledger import Ledger +from lbry import Ledger from lbry.crypto.bip32 import PubKey, PrivateKey, from_extended_key_string -from tests.unit.wallet.key_fixtures import ( +from tests.unit.crypto.key_fixtures import ( expected_ids, expected_privkeys, expected_hardened_privkeys ) diff --git a/tests/unit/service/test_parser.py b/tests/unit/service/test_parser.py index f202a67e4..534b18dbc 100644 --- a/tests/unit/service/test_parser.py +++ b/tests/unit/service/test_parser.py @@ -1,4 +1,5 @@ from unittest import TestCase +from textwrap import dedent from lbry.service.api import Paginated, Wallet from lbry.service.parser import ( parse_method, get_expanders, get_api_definitions, @@ -14,21 +15,34 @@ class FakeAPI: self, name: str, # the name value1='hi', # the first value - value2=9 # the second value + value2=9, # the second value + _ignored=9 ) -> str: # thing name """create command doc""" def thing_list( self, value1: str = None, # the first value - value2: int = None, # the second value + value2: int = None, # the second value with a very very long description which needs to be wrapped value3=False, # a bool # multi-line **pagination_kwargs ) -> Paginated[Wallet]: # list of wallets """list command doc""" - def not_grouped(self) -> str: # some string + def thing_update(self, value1: str) -> Wallet: # updated wallet + """update command doc""" + + def thing_delete(self, value1: str, **tx_and_pagination_kwargs) -> Wallet: # deleted thing + """ + delete command doc + + Usage: + thing delete + {kwargs} + """ + + def not_grouped(self) -> str: # cheese """ group command doc @@ -39,7 +53,7 @@ class FakeAPI: --foo : (bool) blah Returns: - (str) blah + foo bar """ @@ -71,10 +85,19 @@ class TestParser(TestCase): 'method': FakeAPI.thing_list, 'arguments': [ {'name': 'value1', 'type': 'str', 'desc': ['the first value']}, - {'name': 'value2', 'type': 'int', 'desc': ['the second value']}, + {'name': 'value2', 'type': 'int', 'desc': [ + 'the second value with a very very long description which needs to be wrapped']}, {'name': 'value3', 'type': 'bool', 'default': False, 'desc': ['a bool', 'multi-line']}, - {'name': 'page', 'type': 'int', 'desc': ['page to return during paginating']}, - {'name': 'page_size', 'type': 'int', 'desc': ['number of items on page during pagination']} + {'name': 'page', 'type': 'int', 'desc': ['page to return for paginating']}, + {'name': 'page_size', 'type': 'int', 'desc': ['number of items on page for pagination']}, + {'name': 'include_total', 'type': 'bool', 'default': False, + 'desc': ['calculate total number of items and pages']}, + ], + 'kwargs': [ + {'name': 'page', 'type': 'int', 'desc': ['page to return for paginating']}, + {'name': 'page_size', 'type': 'int', 'desc': ['number of items on page for pagination']}, + {'name': 'include_total', 'type': 'bool', 'default': False, + 'desc': ['calculate total number of items and pages']}, ], 'returns': { 'type': 'Paginated[Wallet]', @@ -91,6 +114,21 @@ class TestParser(TestCase): } } ) + self.assertEqual( + parse_method(FakeAPI.thing_update, expanders), { + 'name': 'thing_update', + 'desc': {'text': ['update command doc']}, + 'method': FakeAPI.thing_update, + 'arguments': [ + {'name': 'value1', 'type': 'str', 'desc': []}, + ], + 'returns': { + 'type': 'Wallet', + 'desc': ['updated wallet'], + 'json': {'id': 'wallet_id', 'name': 'optional wallet name'}, + } + } + ) self.assertEqual( parse_method(FakeAPI.not_grouped, expanders), { 'name': 'not_grouped', @@ -98,14 +136,32 @@ class TestParser(TestCase): 'text': ['group command doc'], 'usage': [' not_grouped [--foo]'], 'options': [' --foo : (bool) blah'], - 'returns': [' (str) blah'] + 'returns': [' foo bar'] }, 'method': FakeAPI.not_grouped, 'arguments': [], - 'returns': {'desc': ['some string'], 'type': 'str'} + 'returns': {'desc': ['cheese'], 'type': 'str'} } ) + +class TestGenerator(TestCase): + maxDiff = None + + def test_generate_options(self): + expanders = get_expanders() + self.assertEqual( + generate_options(parse_method(FakeAPI.thing_list, expanders), indent=' '), [ + ' --value1= : (str) the first value', + ' --value2= : (int) the second value with a very very long description which', + ' needs to be wrapped', + ' --value3 : (bool) a bool multi-line', + ' --page= : (int) page to return for paginating', + ' --page_size= : (int) number of items on page for pagination', + ' --include_total : (bool) calculate total number of items and pages', + ] + ) + def test_get_api_definitions(self): defs = get_api_definitions(FakeAPI) self.assertEqual({'groups', 'commands'}, set(defs)) @@ -116,19 +172,96 @@ class TestParser(TestCase): self.assertEqual(defs['commands']['thing_list']['name'], 'list') self.assertEqual(defs['commands']['not_grouped']['name'], 'not_grouped') self.assertNotIn('group', defs['commands']['not_grouped']) - - -class TestGenerator(TestCase): - maxDiff = None - - def test_generate_options(self): - expanders = get_expanders() self.assertEqual( - generate_options(parse_method(FakeAPI.thing_list, expanders), indent=' '), [ - ' --value1= : (str) the first value', - ' --value2= : (int) the second value', - ' --value3 : (bool) a bool multi-line [default: False]', - ' --page= : (int) page to return during paginating', - ' --page_size= : (int) number of items on page during pagination' - ] + defs['commands']['thing_create']['help'], + dedent("""\ + create command doc + + Usage: + thing create + + Options: + --name= : (str) the name + --value1= : (str) the first value [default: 'hi'] + --value2= : (int) the second value [default: 9] + + Returns: + (str) thing name""") + ) + self.assertEqual( + defs['commands']['thing_delete']['help'], + dedent("""\ + delete command doc + + Usage: + thing delete + [--wallet_id=] [--change_account_id=] + [--fund_account_id=...] [--preview] [--blocking] + [--page=] [--page_size=] [--include_total] + + Options: + --value1= : (str) + --wallet_id= : (str) restrict operation to specific wallet + --change_account_id= : (str) account to send excess change (LBC) + --fund_account_id= : (str, list) accounts to fund the transaction + --preview : (bool) do not broadcast the transaction + --blocking : (bool) wait until transaction is in mempool + --page= : (int) page to return for paginating + --page_size= : (int) number of items on page for pagination + --include_total : (bool) calculate total number of items and + pages + + Returns: + (Wallet) deleted thing + { + "id": "wallet_id", + "name": "optional wallet name" + }""") + ) + self.assertEqual( + defs['commands']['thing_list']['help'], + dedent("""\ + list command doc + + Usage: + thing list + + Options: + --value1= : (str) the first value + --value2= : (int) the second value with a very very long description + which needs to be wrapped + --value3 : (bool) a bool multi-line + --page= : (int) page to return for paginating + --page_size= : (int) number of items on page for pagination + --include_total : (bool) calculate total number of items and pages + + Returns: + (Paginated[Wallet]) list of wallets + { + "page": "Page number of the current items.", + "page_size": "Number of items to show on a page.", + "total_pages": "Total number of pages.", + "total_items": "Total number of items.", + "items": [ + { + "id": "wallet_id", + "name": "optional wallet name" + } + ] + }""") + ) + self.assertEqual( + defs['commands']['not_grouped']['help'], + dedent("""\ + group command doc + + Usage: + not_grouped [--foo] + + Options: + --foo : (bool) blah + + Returns: + (str) cheese + foo bar""") ) diff --git a/tests/unit/test_conf.py b/tests/unit/test_conf.py index cae6e6a90..6e69d8a13 100644 --- a/tests/unit/test_conf.py +++ b/tests/unit/test_conf.py @@ -4,9 +4,11 @@ import types import tempfile import unittest import argparse -import lbry.wallet -from lbry.conf import Config, BaseConfig, String, Integer, Toggle, Servers, Strings, StringChoice, NOT_SET from lbry.error import InvalidCurrencyError +from lbry.conf import ( + Config, BaseConfig, String, Integer, Toggle, + Servers, Strings, StringChoice, NOT_SET +) class TestConfig(BaseConfig): diff --git a/tests/unit/wallet/key_fixtures.py b/tests/unit/wallet/key_fixtures.py deleted file mode 100644 index a5cdc04c7..000000000 --- a/tests/unit/wallet/key_fixtures.py +++ /dev/null @@ -1,65 +0,0 @@ -expected_ids = [ - b'948adae2a128c0bd1fa238117fd0d9690961f26e', - b'cd9f4f2adde7de0a53ab6d326bb6a62b489876dd', - b'c479e02a74a809ffecff60255d1c14f4081a197a', - b'4bab2fb2c424f31f170b15ec53c4a596db9d6710', - b'689cb7c621f57b7c398e7e04ed9a5098ab8389e9', - b'75116d6a689a0f9b56fe7cfec9cbbd0e16814288', - b'2439f0993fb298497dd7f317b9737c356f664a86', - b'32f1cb4799008cf5496bb8cafdaf59d5dabec6af', - b'fa29aa536353904e9cc813b0cf18efcc09e5ad13', - b'37df34002f34d7875428a2977df19be3f4f40a31', - b'8c8a72b5d2747a3e7e05ed85110188769d5656c3', - b'e5c8ef10c5bdaa79c9a237a096f50df4dcac27f0', - b'4d5270dc100fba85974665c20cd0f95d4822e8d1', - b'e76b07da0cdd59915475cd310599544b9744fa34', - b'6f009bccf8be99707161abb279d8ccf8fd953721', - b'f32f08b722cc8607c3f7f192b4d5f13a74c85785', - b'46f4430a5c91b9b799e9be6b47ac7a749d8d9f30', - b'ebbf9850abe0aae2d09e7e3ebd6b51f01282f39b', - b'5f6655438f8ddc6b2f6ea8197c8babaffc9f5c09', - b'e194e70ee8711b0ed765608121e4cceb551cdf28' -] -expected_privkeys = [ - b'95557ee9a2bb7665e67e45246658b5c839f7dcd99b6ebc800eeebccd28bf134a', - b'689b6921f65647a8e4fc1497924730c92ad4ad183f10fac2bdee65cc8fb6dcf9', - b'977ee018b448c530327b7e927cc3645ca4cb152c5dd98e1bd917c52fd46fc80a', - b'3c7fb05b0ab4da8b292e895f574f8213cadfe81b84ded7423eab61c5f884c8ae', - b'b21fc7be1e69182827538683a48ac9d95684faf6c1c6deabb6e513d8c76afcc9', - b'a5021734dbbf1d090b15509ba00f2c04a3d5afc19939b4594ca0850d4190b923', - b'07dfe0aa94c1b948dc935be1f8179f3050353b46f3a3134e77c70e66208be72d', - b'c331b2fb82cd91120b0703ee312042a854a51a8d945aa9e70fb14d68b0366fe1', - b'3aa59ec4d8f1e7ce2775854b5e82433535b6e3503f9a8e7c4e60aac066d44718', - b'ccc8b4ca73b266b4a0c89a9d33c4ec7532b434c9294c26832355e5e2bee2e005', - b'280c074d8982e56d70c404072252c309694a6e5c05457a6abbe8fc225c2dfd52', - b'546cee26da713a3a64b2066d5e3a52b7c1d927396d1ba8a3d9f6e3e973398856', - b'7fbc4615d5e819eee22db440c5bcc4ff25bb046841c41a192003a6d9abfbafbf', - b'5b63f13011cab965feea3a41fac2d7a877aa710ab20e2a9a1708474e3c05c050', - b'394b36f528947557d317fd40a4adde5514c8745a5f64185421fa2c0c4a158938', - b'8f101c8f5290ae6c0dd76d210b7effacd7f12db18f3befab711f533bde084c76', - b'6637a656f897a66080fbe60027d32c3f4ebc0e3b5f96123a33f932a091b039c2', - b'2815aa6667c042a3a4565fb789890cd33e380d047ed712759d097d479df71051', - b'120e761c6382b07a9548650a20b3b9dd74b906093260fa6f92f790ba71f79e8d', - b'823c8a613ea539f730a968518993195174bf973ed75c734b6898022867165d7b' -] -expected_hardened_privkeys = [ - b'abdba45b0459e7804beb68edb899e58a5c2636bf67d096711904001406afbd4c', - b'c9e804d4b8fdd99ef6ab2b0ca627a57f4283c28e11e9152ad9d3f863404d940e', - b'4cf87d68ae99711261f8cb8e1bde83b8703ff5d689ef70ce23106d1e6e8ed4bd', - b'dbf8d578c77f9bf62bb2ad40975e253af1e1d44d53abf84a22d2be29b9488f7f', - b'633bb840505521ffe39cb89a04fb8bff3298d6b64a5d8f170aca1e456d6f89b9', - b'92e80a38791bd8ba2105b9867fd58ac2cc4fb9853e18141b7fee1884bc5aae69', - b'd3663339af1386d05dd90ee20f627661ae87ddb1db0c2dc73fd8a4485930d0e7', - b'09a448303452d241b8a25670b36cc758975b97e88f62b6f25cd9084535e3c13a', - b'ee22eb77df05ff53e9c2ba797c1f2ebf97ec4cf5a99528adec94972674aeabed', - b'935facccb6120659c5b7c606a457c797e5a10ce4a728346e1a3a963251169651', - b'8ac9b4a48da1def375640ca03bc6711040dfd4eea7106d42bb4c2de83d7f595e', - b'51ecd3f7565c2b86d5782dbde2175ab26a7b896022564063fafe153588610be9', - b'04918252f6b6f51cd75957289b56a324b45cc085df80839137d740f9ada6c062', - b'2efbd0c839af971e3769c26938d776990ebf097989df4861535a7547a2701483', - b'85c6e31e6b27bd188291a910f4a7faba7fceb3e09df72884b10907ecc1491cd0', - b'05e245131885bebda993a31bb14ac98b794062a50af639ad22010aed1e533a54', - b'ddca42cf7db93f3a3f0723d5fee4c21bf60b7afac35d5c30eb34bd91b35cc609', - b'324a5c16030e0c3947e4dcd2b5057fd3a4d5bed96b23e3b476b2af0ab76369c9', - b'da63c41cdb398cdcd93e832f3e198528afbb4065821b026c143cec910d8362f0' -] diff --git a/tests/unit/wallet/test_account.py b/tests/unit/wallet/test_account.py index 91dbf4564..a9b41d180 100644 --- a/tests/unit/wallet/test_account.py +++ b/tests/unit/wallet/test_account.py @@ -1,8 +1,7 @@ from lbry.testcase import AsyncioTestCase - -from lbry.blockchain.ledger import Ledger -from lbry.wallet.account import Account, SingleKey, HierarchicalDeterministic -from lbry.db import Database, PubkeyAddress +from lbry.blockchain import Ledger +from lbry.db import Database, tables +from lbry.wallet import Account, SingleKey, HierarchicalDeterministic class AccountTestCase(AsyncioTestCase): @@ -15,43 +14,35 @@ class AccountTestCase(AsyncioTestCase): async def update_addressed_used(self, address, used): await self.db.execute( - PubkeyAddress.update() - .where(PubkeyAddress.c.address == address) - .values(used_times=used) + tables.PubkeyAddress.update() + .where(tables.PubkeyAddress.c.address == address) + .values(used_times=used) ) -class TestAccount(AccountTestCase): +class TestHierarchicalDeterministicAccount(AccountTestCase): async def test_generate_account(self): - account = Account.generate(self.ledger, self.db, 'lbryum') - self.assertIsNotNone(account.seed) + account = await Account.generate(self.ledger, self.db) + self.assertEqual(account.ledger, self.ledger) + self.assertEqual(account.db, self.db) + self.assertEqual(account.name, f'Account #{account.public_key.address}') + self.assertEqual(len(account.phrase.split()), 12) + self.assertEqual(account.language, 'en') + self.assertEqual(account.private_key_string, '') + self.assertEqual(account.encrypted, False) self.assertEqual(account.public_key.ledger, self.ledger) self.assertEqual(account.private_key.public_key, account.public_key) - - addresses = await account.receiving.get_addresses() - self.assertEqual(len(addresses), 0) - addresses = await account.change.get_addresses() - self.assertEqual(len(addresses), 0) - - await account.ensure_address_gap() - - addresses = await account.receiving.get_addresses() - self.assertEqual(len(addresses), 20) - addresses = await account.change.get_addresses() - self.assertEqual(len(addresses), 6) - - async def test_generate_keys_over_batch_threshold_saves_it_properly(self): - account = Account.generate(self.ledger, self.db, 'lbryum') - async with account.receiving.address_generator_lock: - await account.receiving._generate_keys(0, 200) - records = await account.receiving.get_address_records() - self.assertEqual(len(records), 201) + self.assertIsInstance(account.receiving, HierarchicalDeterministic) + self.assertIsInstance(account.change, HierarchicalDeterministic) async def test_ensure_address_gap(self): - account = Account.generate(self.ledger, self.db, 'lbryum') - - self.assertIsInstance(account.receiving, HierarchicalDeterministic) + account = await Account.generate(self.ledger, self.db, 'lbryum') + self.assertEqual(len(await account.receiving.get_addresses()), 0) + self.assertEqual(len(await account.change.get_addresses()), 0) + await account.ensure_address_gap() + self.assertEqual(len(await account.receiving.get_addresses()), 20) + self.assertEqual(len(await account.change.get_addresses()), 6) async with account.receiving.address_generator_lock: await account.receiving._generate_keys(4, 7) @@ -87,6 +78,13 @@ class TestAccount(AccountTestCase): new_keys = await account.receiving.ensure_address_gap() self.assertEqual(len(new_keys), 20) + async def test_generate_keys_over_batch_threshold_saves_it_properly(self): + account = Account.generate(self.ledger, self.db, 'lbryum') + async with account.receiving.address_generator_lock: + await account.receiving._generate_keys(0, 200) + records = await account.receiving.get_address_records() + self.assertEqual(len(records), 201) + async def test_get_or_create_usable_address(self): account = Account.generate(self.ledger, self.db, 'lbryum') @@ -100,7 +98,7 @@ class TestAccount(AccountTestCase): self.assertEqual(len(keys), 20) async def test_generate_account_from_seed(self): - account = Account.from_dict( + account = await Account.from_dict( self.ledger, self.db, { "seed": "carbon smart garage balance margin twelve chest sword toas" @@ -382,8 +380,8 @@ class AccountEncryptionTests(AccountTestCase): self.assertFalse(account.encrypted) - def test_decrypt_wallet(self): - account = Account.from_dict(self.ledger, self.db, self.encrypted_account) + async def test_decrypt_wallet(self): + account = await Account.from_dict(self.ledger, self.db, self.encrypted_account) self.assertTrue(account.encrypted) account.decrypt(self.password) diff --git a/tests/unit/wallet/test_coinselection.py b/tests/unit/wallet/test_coinselection.py index 40cbf1684..cfd007132 100644 --- a/tests/unit/wallet/test_coinselection.py +++ b/tests/unit/wallet/test_coinselection.py @@ -1,10 +1,10 @@ from unittest import TestCase from types import GeneratorType -from lbry.blockchain.ledger import RegTestLedger -from lbry.wallet.coinselection import CoinSelector, OutputEffectiveAmountEstimator, MAXIMUM_TRIES +from lbry.blockchain import Ledger from lbry.constants import CENT from lbry.testcase import get_output as utxo +from lbry.wallet.coinselection import CoinSelector, OutputEffectiveAmountEstimator, MAXIMUM_TRIES def search(*args, **kwargs): @@ -15,7 +15,7 @@ def search(*args, **kwargs): class BaseSelectionTestCase(TestCase): def setUp(self): - self.ledger = RegTestLedger() + self.ledger = Ledger() def estimates(self, *args): txos = args[0] if isinstance(args[0], (GeneratorType, list)) else args diff --git a/tests/unit/wallet/test_mnemonic.py b/tests/unit/wallet/test_mnemonic.py index 88edba47a..3de2b534f 100644 --- a/tests/unit/wallet/test_mnemonic.py +++ b/tests/unit/wallet/test_mnemonic.py @@ -3,9 +3,9 @@ from binascii import hexlify from lbry.wallet import words from lbry.wallet.mnemonic import ( - get_languages, is_valid, - sync_generate as generate, - sync_to_seed as to_seed + get_languages, is_phrase_valid as is_valid, + sync_generate_phrase as generate, + sync_derive_key_from_phrase as derive ) @@ -17,7 +17,7 @@ class TestMnemonic(TestCase): for lang in languages: self.assertEqual(len(getattr(words, lang)), 2048) - def test_is_valid(self): + def test_is_phrase_valid(self): self.assertFalse(is_valid('en', '')) self.assertFalse(is_valid('en', 'foo')) self.assertFalse(is_valid('en', 'awesomeball')) @@ -27,13 +27,13 @@ class TestMnemonic(TestCase): self.assertTrue(is_valid('ja', 'るいじ りんご')) self.assertTrue(is_valid('ja', 'るいじ りんご')) - def test_generate(self): + def test_generate_phrase(self): self.assertGreaterEqual(len(generate('en').split()), 11) self.assertGreaterEqual(len(generate('ja').split()), 11) - def test_to_seed(self): + def test_phrase_to_key(self): self.assertEqual( - hexlify(to_seed( + hexlify(derive( "carbon smart garage balance margin twelve che" "st sword toast envelope bottom stomach absent" )), diff --git a/tests/unit/wallet/test_wallet.py b/tests/unit/wallet/test_wallet.py index f2274d162..0bd288a0c 100644 --- a/tests/unit/wallet/test_wallet.py +++ b/tests/unit/wallet/test_wallet.py @@ -171,7 +171,7 @@ class TestWalletCreation(WalletTestCase): self.assertEqual(len(wallet1.accounts), 1) self.assertEqual(wallet1.preferences, {'one': 1, 'conflict': 1}) - added = wallet1.merge('password', wallet2.pack('password')) + added = await wallet1.merge('password', wallet2.pack('password')) self.assertEqual(added[0].id, wallet2.default_account.id) self.assertEqual(len(wallet1.accounts), 2) self.assertEqual(wallet1.accounts[1].id, wallet2.default_account.id)