unit tests updated

This commit is contained in:
Lex Berezhny 2020-05-18 08:28:23 -04:00
parent be6ebf0047
commit 0886a7946e
8 changed files with 206 additions and 138 deletions

View file

@ -1,10 +1,10 @@
from unittest import TestCase from unittest import TestCase
from binascii import unhexlify, hexlify 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 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 expected_ids, expected_privkeys, expected_hardened_privkeys
) )

View file

@ -1,4 +1,5 @@
from unittest import TestCase from unittest import TestCase
from textwrap import dedent
from lbry.service.api import Paginated, Wallet from lbry.service.api import Paginated, Wallet
from lbry.service.parser import ( from lbry.service.parser import (
parse_method, get_expanders, get_api_definitions, parse_method, get_expanders, get_api_definitions,
@ -14,21 +15,34 @@ class FakeAPI:
self, self,
name: str, # the name name: str, # the name
value1='hi', # the first value value1='hi', # the first value
value2=9 # the second value value2=9, # the second value
_ignored=9
) -> str: # thing name ) -> str: # thing name
"""create command doc""" """create command doc"""
def thing_list( def thing_list(
self, self,
value1: str = None, # the first value 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 value3=False, # a bool
# multi-line # multi-line
**pagination_kwargs **pagination_kwargs
) -> Paginated[Wallet]: # list of wallets ) -> Paginated[Wallet]: # list of wallets
"""list command doc""" """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 <value1>
{kwargs}
"""
def not_grouped(self) -> str: # cheese
""" """
group command doc group command doc
@ -39,7 +53,7 @@ class FakeAPI:
--foo : (bool) blah --foo : (bool) blah
Returns: Returns:
(str) blah foo bar
""" """
@ -71,10 +85,19 @@ class TestParser(TestCase):
'method': FakeAPI.thing_list, 'method': FakeAPI.thing_list,
'arguments': [ 'arguments': [
{'name': 'value1', 'type': 'str', 'desc': ['the first value']}, {'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': 'value3', 'type': 'bool', 'default': False, 'desc': ['a bool', 'multi-line']},
{'name': 'page', 'type': 'int', 'desc': ['page to return during paginating']}, {'name': 'page', 'type': 'int', 'desc': ['page to return for paginating']},
{'name': 'page_size', 'type': 'int', 'desc': ['number of items on page during pagination']} {'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': { 'returns': {
'type': 'Paginated[Wallet]', '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( self.assertEqual(
parse_method(FakeAPI.not_grouped, expanders), { parse_method(FakeAPI.not_grouped, expanders), {
'name': 'not_grouped', 'name': 'not_grouped',
@ -98,14 +136,32 @@ class TestParser(TestCase):
'text': ['group command doc'], 'text': ['group command doc'],
'usage': [' not_grouped [--foo]'], 'usage': [' not_grouped [--foo]'],
'options': [' --foo : (bool) blah'], 'options': [' --foo : (bool) blah'],
'returns': [' (str) blah'] 'returns': [' foo bar']
}, },
'method': FakeAPI.not_grouped, 'method': FakeAPI.not_grouped,
'arguments': [], '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=<value1> : (str) the first value',
' --value2=<value2> : (int) the second value with a very very long description which',
' needs to be wrapped',
' --value3 : (bool) a bool multi-line',
' --page=<page> : (int) page to return for paginating',
' --page_size=<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): def test_get_api_definitions(self):
defs = get_api_definitions(FakeAPI) defs = get_api_definitions(FakeAPI)
self.assertEqual({'groups', 'commands'}, set(defs)) 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']['thing_list']['name'], 'list')
self.assertEqual(defs['commands']['not_grouped']['name'], 'not_grouped') self.assertEqual(defs['commands']['not_grouped']['name'], 'not_grouped')
self.assertNotIn('group', defs['commands']['not_grouped']) self.assertNotIn('group', defs['commands']['not_grouped'])
class TestGenerator(TestCase):
maxDiff = None
def test_generate_options(self):
expanders = get_expanders()
self.assertEqual( self.assertEqual(
generate_options(parse_method(FakeAPI.thing_list, expanders), indent=' '), [ defs['commands']['thing_create']['help'],
' --value1=<value1> : (str) the first value', dedent("""\
' --value2=<value2> : (int) the second value', create command doc
' --value3 : (bool) a bool multi-line [default: False]',
' --page=<page> : (int) page to return during paginating', Usage:
' --page_size=<page_size> : (int) number of items on page during pagination' thing create
]
Options:
--name=<name> : (str) the name
--value1=<value1> : (str) the first value [default: 'hi']
--value2=<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 <value1>
[--wallet_id=<wallet_id>] [--change_account_id=<change_account_id>]
[--fund_account_id=<fund_account_id>...] [--preview] [--blocking]
[--page=<page>] [--page_size=<page_size>] [--include_total]
Options:
--value1=<value1> : (str)
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
--change_account_id=<change_account_id> : (str) account to send excess change (LBC)
--fund_account_id=<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=<page> : (int) page to return for paginating
--page_size=<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=<value1> : (str) the first value
--value2=<value2> : (int) the second value with a very very long description
which needs to be wrapped
--value3 : (bool) a bool multi-line
--page=<page> : (int) page to return for paginating
--page_size=<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""")
) )

View file

@ -4,9 +4,11 @@ import types
import tempfile import tempfile
import unittest import unittest
import argparse 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.error import InvalidCurrencyError
from lbry.conf import (
Config, BaseConfig, String, Integer, Toggle,
Servers, Strings, StringChoice, NOT_SET
)
class TestConfig(BaseConfig): class TestConfig(BaseConfig):

View file

@ -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'
]

View file

@ -1,8 +1,7 @@
from lbry.testcase import AsyncioTestCase from lbry.testcase import AsyncioTestCase
from lbry.blockchain import Ledger
from lbry.blockchain.ledger import Ledger from lbry.db import Database, tables
from lbry.wallet.account import Account, SingleKey, HierarchicalDeterministic from lbry.wallet import Account, SingleKey, HierarchicalDeterministic
from lbry.db import Database, PubkeyAddress
class AccountTestCase(AsyncioTestCase): class AccountTestCase(AsyncioTestCase):
@ -15,43 +14,35 @@ class AccountTestCase(AsyncioTestCase):
async def update_addressed_used(self, address, used): async def update_addressed_used(self, address, used):
await self.db.execute( await self.db.execute(
PubkeyAddress.update() tables.PubkeyAddress.update()
.where(PubkeyAddress.c.address == address) .where(tables.PubkeyAddress.c.address == address)
.values(used_times=used) .values(used_times=used)
) )
class TestAccount(AccountTestCase): class TestHierarchicalDeterministicAccount(AccountTestCase):
async def test_generate_account(self): async def test_generate_account(self):
account = Account.generate(self.ledger, self.db, 'lbryum') account = await Account.generate(self.ledger, self.db)
self.assertIsNotNone(account.seed) 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.public_key.ledger, self.ledger)
self.assertEqual(account.private_key.public_key, account.public_key) self.assertEqual(account.private_key.public_key, account.public_key)
self.assertIsInstance(account.receiving, HierarchicalDeterministic)
addresses = await account.receiving.get_addresses() self.assertIsInstance(account.change, HierarchicalDeterministic)
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)
async def test_ensure_address_gap(self): async def test_ensure_address_gap(self):
account = Account.generate(self.ledger, self.db, 'lbryum') account = await Account.generate(self.ledger, self.db, 'lbryum')
self.assertEqual(len(await account.receiving.get_addresses()), 0)
self.assertIsInstance(account.receiving, HierarchicalDeterministic) 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: async with account.receiving.address_generator_lock:
await account.receiving._generate_keys(4, 7) await account.receiving._generate_keys(4, 7)
@ -87,6 +78,13 @@ class TestAccount(AccountTestCase):
new_keys = await account.receiving.ensure_address_gap() new_keys = await account.receiving.ensure_address_gap()
self.assertEqual(len(new_keys), 20) 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): async def test_get_or_create_usable_address(self):
account = Account.generate(self.ledger, self.db, 'lbryum') account = Account.generate(self.ledger, self.db, 'lbryum')
@ -100,7 +98,7 @@ class TestAccount(AccountTestCase):
self.assertEqual(len(keys), 20) self.assertEqual(len(keys), 20)
async def test_generate_account_from_seed(self): async def test_generate_account_from_seed(self):
account = Account.from_dict( account = await Account.from_dict(
self.ledger, self.db, { self.ledger, self.db, {
"seed": "seed":
"carbon smart garage balance margin twelve chest sword toas" "carbon smart garage balance margin twelve chest sword toas"
@ -382,8 +380,8 @@ class AccountEncryptionTests(AccountTestCase):
self.assertFalse(account.encrypted) self.assertFalse(account.encrypted)
def test_decrypt_wallet(self): async def test_decrypt_wallet(self):
account = Account.from_dict(self.ledger, self.db, self.encrypted_account) account = await Account.from_dict(self.ledger, self.db, self.encrypted_account)
self.assertTrue(account.encrypted) self.assertTrue(account.encrypted)
account.decrypt(self.password) account.decrypt(self.password)

View file

@ -1,10 +1,10 @@
from unittest import TestCase from unittest import TestCase
from types import GeneratorType from types import GeneratorType
from lbry.blockchain.ledger import RegTestLedger from lbry.blockchain import Ledger
from lbry.wallet.coinselection import CoinSelector, OutputEffectiveAmountEstimator, MAXIMUM_TRIES
from lbry.constants import CENT from lbry.constants import CENT
from lbry.testcase import get_output as utxo from lbry.testcase import get_output as utxo
from lbry.wallet.coinselection import CoinSelector, OutputEffectiveAmountEstimator, MAXIMUM_TRIES
def search(*args, **kwargs): def search(*args, **kwargs):
@ -15,7 +15,7 @@ def search(*args, **kwargs):
class BaseSelectionTestCase(TestCase): class BaseSelectionTestCase(TestCase):
def setUp(self): def setUp(self):
self.ledger = RegTestLedger() self.ledger = Ledger()
def estimates(self, *args): def estimates(self, *args):
txos = args[0] if isinstance(args[0], (GeneratorType, list)) else args txos = args[0] if isinstance(args[0], (GeneratorType, list)) else args

View file

@ -3,9 +3,9 @@ from binascii import hexlify
from lbry.wallet import words from lbry.wallet import words
from lbry.wallet.mnemonic import ( from lbry.wallet.mnemonic import (
get_languages, is_valid, get_languages, is_phrase_valid as is_valid,
sync_generate as generate, sync_generate_phrase as generate,
sync_to_seed as to_seed sync_derive_key_from_phrase as derive
) )
@ -17,7 +17,7 @@ class TestMnemonic(TestCase):
for lang in languages: for lang in languages:
self.assertEqual(len(getattr(words, lang)), 2048) 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', ''))
self.assertFalse(is_valid('en', 'foo')) self.assertFalse(is_valid('en', 'foo'))
self.assertFalse(is_valid('en', 'awesomeball')) self.assertFalse(is_valid('en', 'awesomeball'))
@ -27,13 +27,13 @@ class TestMnemonic(TestCase):
self.assertTrue(is_valid('ja', 'るいじ りんご')) self.assertTrue(is_valid('ja', 'るいじ りんご'))
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('en').split()), 11)
self.assertGreaterEqual(len(generate('ja').split()), 11) self.assertGreaterEqual(len(generate('ja').split()), 11)
def test_to_seed(self): def test_phrase_to_key(self):
self.assertEqual( self.assertEqual(
hexlify(to_seed( hexlify(derive(
"carbon smart garage balance margin twelve che" "carbon smart garage balance margin twelve che"
"st sword toast envelope bottom stomach absent" "st sword toast envelope bottom stomach absent"
)), )),

View file

@ -171,7 +171,7 @@ class TestWalletCreation(WalletTestCase):
self.assertEqual(len(wallet1.accounts), 1) self.assertEqual(len(wallet1.accounts), 1)
self.assertEqual(wallet1.preferences, {'one': 1, 'conflict': 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(added[0].id, wallet2.default_account.id)
self.assertEqual(len(wallet1.accounts), 2) self.assertEqual(len(wallet1.accounts), 2)
self.assertEqual(wallet1.accounts[1].id, wallet2.default_account.id) self.assertEqual(wallet1.accounts[1].id, wallet2.default_account.id)