forked from LBRYCommunity/lbry-sdk
f9ff3e5d14
* Removes mock from setup.py dependencies * Refactors name resetTime to reset_time * Replaces import mock with built-in unittest.mock * Refactors imports to separate built-in, external, and project modules
311 lines
14 KiB
Python
311 lines
14 KiB
Python
from unittest import mock
|
|
import json
|
|
import random
|
|
from os import path
|
|
|
|
from twisted.internet import defer
|
|
from twisted.trial import unittest
|
|
|
|
from faker import Faker
|
|
from lbrynet import conf
|
|
from lbrynet.schema.decode import smart_decode
|
|
from lbrynet.extras.daemon.storage import SQLiteStorage
|
|
from lbrynet.extras.daemon.ComponentManager import ComponentManager
|
|
from lbrynet.extras.daemon.Components import DATABASE_COMPONENT, DHT_COMPONENT, WALLET_COMPONENT
|
|
from lbrynet.extras.daemon.Components import f2d
|
|
from lbrynet.extras.daemon.Components import HASH_ANNOUNCER_COMPONENT, REFLECTOR_COMPONENT
|
|
from lbrynet.extras.daemon.Components import UPNP_COMPONENT, BLOB_COMPONENT
|
|
from lbrynet.extras.daemon.Components import PEER_PROTOCOL_SERVER_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT
|
|
from lbrynet.extras.daemon.Components import RATE_LIMITER_COMPONENT, HEADERS_COMPONENT, FILE_MANAGER_COMPONENT
|
|
from lbrynet.extras.daemon.Daemon import Daemon as LBRYDaemon
|
|
from lbrynet.blob.EncryptedFileDownloader import ManagedEncryptedFileDownloader
|
|
from lbrynet.extras.wallet import LbryWalletManager
|
|
from torba.client.wallet import Wallet
|
|
|
|
from lbrynet.p2p.PaymentRateManager import OnlyFreePaymentsManager
|
|
from tests import test_utils
|
|
from tests.mocks import mock_conf_settings, FakeNetwork, FakeFileManager
|
|
from tests.mocks import ExchangeRateManager as DummyExchangeRateManager
|
|
from tests.mocks import BTCLBCFeed, USDBTCFeed
|
|
from tests.test_utils import is_android
|
|
|
|
|
|
def get_test_daemon(data_rate=None, generous=True, with_fee=False):
|
|
if data_rate is None:
|
|
data_rate = conf.ADJUSTABLE_SETTINGS['data_rate'][1]
|
|
rates = {
|
|
'BTCLBC': {'spot': 3.0, 'ts': test_utils.DEFAULT_ISO_TIME + 1},
|
|
'USDBTC': {'spot': 2.0, 'ts': test_utils.DEFAULT_ISO_TIME + 2}
|
|
}
|
|
component_manager = ComponentManager(
|
|
skip_components=[DATABASE_COMPONENT, DHT_COMPONENT, WALLET_COMPONENT, UPNP_COMPONENT,
|
|
PEER_PROTOCOL_SERVER_COMPONENT, REFLECTOR_COMPONENT, HASH_ANNOUNCER_COMPONENT,
|
|
EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT,
|
|
HEADERS_COMPONENT, RATE_LIMITER_COMPONENT],
|
|
file_manager=FakeFileManager
|
|
)
|
|
daemon = LBRYDaemon(component_manager=component_manager)
|
|
daemon.payment_rate_manager = OnlyFreePaymentsManager()
|
|
daemon.wallet_manager = mock.Mock(spec=LbryWalletManager)
|
|
daemon.wallet_manager.wallet = mock.Mock(spec=Wallet)
|
|
daemon.wallet_manager.wallet.use_encryption = False
|
|
daemon.wallet_manager.network = FakeNetwork()
|
|
daemon.storage = mock.Mock(spec=SQLiteStorage)
|
|
market_feeds = [BTCLBCFeed(), USDBTCFeed()]
|
|
daemon.exchange_rate_manager = DummyExchangeRateManager(market_feeds, rates)
|
|
daemon.file_manager = component_manager.get_component(FILE_MANAGER_COMPONENT)
|
|
|
|
metadata = {
|
|
"author": "fake author",
|
|
"language": "en",
|
|
"content_type": "fake/format",
|
|
"description": "fake description",
|
|
"license": "fake license",
|
|
"license_url": "fake license url",
|
|
"nsfw": False,
|
|
"sources": {
|
|
"lbry_sd_hash": 'd2b8b6e907dde95245fe6d144d16c2fdd60c4e0c6463ec98'
|
|
'b85642d06d8e9414e8fcfdcb7cb13532ec5454fb8fe7f280'
|
|
},
|
|
"thumbnail": "fake thumbnail",
|
|
"title": "fake title",
|
|
"ver": "0.0.3"
|
|
}
|
|
if with_fee:
|
|
metadata.update(
|
|
{"fee": {"USD": {"address": "bQ6BGboPV2SpTMEP7wLNiAcnsZiH8ye6eA", "amount": 0.75}}})
|
|
migrated = smart_decode(json.dumps(metadata))
|
|
daemon._resolve = daemon.wallet_manager.resolve = lambda *_: defer.succeed(
|
|
{"test": {'claim': {'value': migrated.claim_dict}}})
|
|
return daemon
|
|
|
|
|
|
class TestCostEst(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
mock_conf_settings(self)
|
|
test_utils.reset_time(self)
|
|
|
|
@defer.inlineCallbacks
|
|
def test_fee_and_generous_data(self):
|
|
size = 10000000
|
|
correct_result = 4.5
|
|
daemon = get_test_daemon(generous=True, with_fee=True)
|
|
result = yield f2d(daemon.get_est_cost("test", size))
|
|
self.assertEqual(result, correct_result)
|
|
|
|
@defer.inlineCallbacks
|
|
def test_fee_and_ungenerous_data(self):
|
|
size = 10000000
|
|
fake_fee_amount = 4.5
|
|
data_rate = conf.ADJUSTABLE_SETTINGS['data_rate'][1]
|
|
correct_result = size / 10 ** 6 * data_rate + fake_fee_amount
|
|
daemon = get_test_daemon(generous=False, with_fee=True)
|
|
result = yield f2d(daemon.get_est_cost("test", size))
|
|
self.assertEqual(result, round(correct_result, 1))
|
|
|
|
@defer.inlineCallbacks
|
|
def test_generous_data_and_no_fee(self):
|
|
size = 10000000
|
|
correct_result = 0.0
|
|
daemon = get_test_daemon(generous=True)
|
|
result = yield f2d(daemon.get_est_cost("test", size))
|
|
self.assertEqual(result, correct_result)
|
|
|
|
@defer.inlineCallbacks
|
|
def test_ungenerous_data_and_no_fee(self):
|
|
size = 10000000
|
|
data_rate = conf.ADJUSTABLE_SETTINGS['data_rate'][1]
|
|
correct_result = size / 10 ** 6 * data_rate
|
|
daemon = get_test_daemon(generous=False)
|
|
result = yield f2d(daemon.get_est_cost("test", size))
|
|
self.assertEqual(result, round(correct_result, 1))
|
|
|
|
|
|
class TestJsonRpc(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
def noop():
|
|
return None
|
|
|
|
mock_conf_settings(self)
|
|
test_utils.reset_time(self)
|
|
self.test_daemon = get_test_daemon()
|
|
self.test_daemon.wallet_manager.is_first_run = False
|
|
self.test_daemon.wallet_manager.get_best_blockhash = noop
|
|
|
|
def test_status(self):
|
|
d = defer.maybeDeferred(self.test_daemon.jsonrpc_status)
|
|
d.addCallback(lambda status: self.assertDictContainsSubset({'is_running': False}, status))
|
|
|
|
def test_help(self):
|
|
d = defer.maybeDeferred(self.test_daemon.jsonrpc_help, command='status')
|
|
d.addCallback(lambda result: self.assertSubstring('daemon status', result['help']))
|
|
# self.assertSubstring('daemon status', d.result)
|
|
|
|
if is_android():
|
|
test_help.skip = "Test cannot pass on Android because PYTHONOPTIMIZE removes the docstrings."
|
|
|
|
|
|
class TestFileListSorting(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
mock_conf_settings(self)
|
|
test_utils.reset_time(self)
|
|
self.faker = Faker('en_US')
|
|
self.faker.seed(129) # contains 3 same points paid (5.9)
|
|
self.test_daemon = get_test_daemon()
|
|
self.test_daemon.file_manager.lbry_files = self._get_fake_lbry_files()
|
|
|
|
self.test_points_paid = [
|
|
2.5, 4.8, 5.9, 5.9, 5.9, 6.1, 7.1, 8.2, 8.4, 9.1
|
|
]
|
|
self.test_file_names = [
|
|
'add.mp3', 'any.mov', 'day.tiff', 'decade.odt', 'different.json', 'hotel.bmp',
|
|
'might.bmp', 'physical.json', 'remember.mp3', 'than.ppt'
|
|
]
|
|
self.test_authors = [
|
|
'ashlee27', 'bfrederick', 'brittanyhicks', 'davidsonjeffrey', 'heidiherring',
|
|
'jlewis', 'kswanson', 'michelle50', 'richard64', 'xsteele'
|
|
]
|
|
return self.test_daemon.component_manager.setup()
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_points_paid_no_direction_specified(self):
|
|
sort_options = ['points_paid']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(self.test_points_paid, [f['points_paid'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_points_paid_ascending(self):
|
|
sort_options = ['points_paid,asc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(self.test_points_paid, [f['points_paid'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_points_paid_descending(self):
|
|
sort_options = ['points_paid, desc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(list(reversed(self.test_points_paid)), [f['points_paid'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_file_name_no_direction_specified(self):
|
|
sort_options = ['file_name']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(self.test_file_names, [f['file_name'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_file_name_ascending(self):
|
|
sort_options = ['file_name,\nasc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(self.test_file_names, [f['file_name'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_file_name_descending(self):
|
|
sort_options = ['\tfile_name,\n\tdesc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(list(reversed(self.test_file_names)), [f['file_name'] for f in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_multiple_criteria(self):
|
|
expected = [
|
|
'file_name=different.json, points_paid=9.1',
|
|
'file_name=physical.json, points_paid=8.4',
|
|
'file_name=any.mov, points_paid=8.2',
|
|
'file_name=hotel.bmp, points_paid=7.1',
|
|
'file_name=add.mp3, points_paid=6.1',
|
|
'file_name=decade.odt, points_paid=5.9',
|
|
'file_name=might.bmp, points_paid=5.9',
|
|
'file_name=than.ppt, points_paid=5.9',
|
|
'file_name=remember.mp3, points_paid=4.8',
|
|
'file_name=day.tiff, points_paid=2.5'
|
|
]
|
|
format_result = lambda f: 'file_name={}, points_paid={}'.format(f['file_name'], f['points_paid'])
|
|
|
|
sort_options = ['file_name,asc', 'points_paid,desc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(expected, [format_result(r) for r in file_list])
|
|
|
|
# Check that the list is not sorted as expected when sorted only by file_name.
|
|
sort_options = ['file_name,asc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertNotEqual(expected, [format_result(r) for r in file_list])
|
|
|
|
# Check that the list is not sorted as expected when sorted only by points_paid.
|
|
sort_options = ['points_paid,desc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertNotEqual(expected, [format_result(r) for r in file_list])
|
|
|
|
# Check that the list is not sorted as expected when not sorted at all.
|
|
file_list = yield self.test_daemon.jsonrpc_file_list()
|
|
self.assertNotEqual(expected, [format_result(r) for r in file_list])
|
|
|
|
@defer.inlineCallbacks
|
|
def test_sort_by_nested_field(self):
|
|
extract_authors = lambda file_list: [f['metadata']['author'] for f in file_list]
|
|
|
|
sort_options = ['metadata.author']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(self.test_authors, extract_authors(file_list))
|
|
|
|
# Check that the list matches the expected in reverse when sorting in descending order.
|
|
sort_options = ['metadata.author,desc']
|
|
file_list = yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
self.assertEqual(list(reversed(self.test_authors)), extract_authors(file_list))
|
|
|
|
# Check that the list is not sorted as expected when not sorted at all.
|
|
file_list = yield self.test_daemon.jsonrpc_file_list()
|
|
self.assertNotEqual(self.test_authors, extract_authors(file_list))
|
|
|
|
@defer.inlineCallbacks
|
|
def test_invalid_sort_produces_meaningful_errors(self):
|
|
sort_options = ['meta.author']
|
|
expected_message = "Failed to get 'meta.author', key 'meta' was not found."
|
|
with self.assertRaisesRegex(Exception, expected_message):
|
|
yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
sort_options = ['metadata.foo.bar']
|
|
expected_message = "Failed to get 'metadata.foo.bar', key 'foo' was not found."
|
|
with self.assertRaisesRegex(Exception, expected_message):
|
|
yield self.test_daemon.jsonrpc_file_list(sort=sort_options)
|
|
|
|
def _get_fake_lbry_files(self):
|
|
return [self._get_fake_lbry_file() for _ in range(10)]
|
|
|
|
def _get_fake_lbry_file(self):
|
|
lbry_file = mock.Mock(spec=ManagedEncryptedFileDownloader)
|
|
|
|
file_path = self.faker.file_path()
|
|
stream_name = self.faker.file_name()
|
|
channel_claim_id = self.faker.sha1()
|
|
channel_name = self.faker.simple_profile()['username']
|
|
faked_attributes = {
|
|
'channel_claim_id': channel_claim_id,
|
|
'channel_name': '@' + channel_name,
|
|
'claim_id': self.faker.sha1(),
|
|
'claim_name': '-'.join(self.faker.words(4)),
|
|
'completed': self.faker.boolean(),
|
|
'download_directory': path.dirname(file_path),
|
|
'download_path': file_path,
|
|
'file_name': path.basename(file_path),
|
|
'key': self.faker.md5(raw_output=True),
|
|
'metadata': {
|
|
'author': channel_name,
|
|
'nsfw': random.randint(0, 1) == 1,
|
|
},
|
|
'mime_type': self.faker.mime_type(),
|
|
'nout': abs(self.faker.pyint()),
|
|
'outpoint': self.faker.md5() + self.faker.md5(),
|
|
'points_paid': self.faker.pyfloat(left_digits=1, right_digits=1, positive=True),
|
|
'sd_hash': self.faker.md5() + self.faker.md5() + self.faker.md5(),
|
|
'stopped': self.faker.boolean(),
|
|
'stream_hash': self.faker.md5() + self.faker.md5() + self.faker.md5(),
|
|
'stream_name': stream_name,
|
|
'suggested_file_name': stream_name,
|
|
'txid': self.faker.md5() + self.faker.md5(),
|
|
'written_bytes': self.faker.pyint(),
|
|
}
|
|
|
|
for key in faked_attributes:
|
|
setattr(lbry_file, key, faked_attributes[key])
|
|
|
|
return lbry_file
|