From 7860b956ffc66cdc52a9f510eb9f7475e5eef8f2 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Wed, 27 Mar 2019 16:02:17 -0400 Subject: [PATCH] fixes and tests for publish command --- lbrynet/extras/daemon/Daemon.py | 56 +++++++++++++++--------- tests/integration/test_claim_commands.py | 43 ++++++++++++++++++ tests/integration/testcase.py | 9 ++++ 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/lbrynet/extras/daemon/Daemon.py b/lbrynet/extras/daemon/Daemon.py index edecd070b..697fab3b0 100644 --- a/lbrynet/extras/daemon/Daemon.py +++ b/lbrynet/extras/daemon/Daemon.py @@ -1967,7 +1967,7 @@ class Daemon(metaclass=JSONRPCServerType): Create or update a stream claim at a given name (use 'stream create/update' for more control). Usage: - publish ( | --name=) ( | --bid=) ( | --file_path=) + publish ( | --name=) [--bid=] [--file_path=] [ | --stream_type=] [--tags=...] [--fee_currency=] [--fee_amount=] [--fee_address=] [--title=] [--description=<description>] [--author=<author>] [--language=<language>] @@ -1975,7 +1975,8 @@ class Daemon(metaclass=JSONRPCServerType): [--release_time=<release_time>] [--video_width=<video_width>] [--video_height=<video_height>] [--video_duration=<video_duration>] [--image_width=<image_width>] [--image_height=<image_height>] [--audio_duration=<audio_duration>] - [--channel_id=<channel_id>] [--channel_account_id=<channel_account_id>...] + [--channel_id=<channel_id>] [--channel_name=<channel_name>] + [--channel_account_id=<channel_account_id>...] [--account_id=<account_id>] [--claim_address=<claim_address>] [--preview] Options: @@ -2011,6 +2012,7 @@ class Daemon(metaclass=JSONRPCServerType): --audio_duration=<duration> : (int) audio duration in seconds, an attempt will be made to calculate this automatically if not provided --channel_id=<channel_id> : (str) claim id of the publisher channel + --channel_name=<channel_name> : (str) name of publisher channel --channel_account_id=<channel_id>: (str) one or more account ids for accounts to look in for channel certificates, defaults to all accounts. --account_id=<account_id> : (str) account to use for funding the transaction @@ -2022,6 +2024,10 @@ class Daemon(metaclass=JSONRPCServerType): account = self.get_account_or_default(kwargs.get('account_id')) claims = await account.get_claims(claim_name=name) if len(claims) == 0: + if 'bid' not in kwargs: + raise Exception("'bid' is a required argument for new publishes.") + if 'file_path' not in kwargs: + raise Exception("'file_path' is a required argument for new publishes.") return await self.jsonrpc_stream_create(name, **kwargs) elif len(claims) == 1: assert claims[0].claim.is_stream, f"Claim at name '{name}' is not a stream claim." @@ -2035,7 +2041,7 @@ class Daemon(metaclass=JSONRPCServerType): conditions=[WALLET_IS_UNLOCKED]) async def jsonrpc_stream_create( self, name, bid, file_path, allow_duplicate_name=False, - channel_id=None, channel_account_id=None, + channel_id=None, channel_name=None, channel_account_id=None, account_id=None, claim_address=None, preview=False, **kwargs): """ Make a new stream claim and announce the associated file to lbrynet. @@ -2050,7 +2056,8 @@ class Daemon(metaclass=JSONRPCServerType): [--release_time=<release_time>] [--video_width=<video_width>] [--video_height=<video_height>] [--video_duration=<video_duration>] [--image_width=<image_width>] [--image_height=<image_height>] [--audio_duration=<audio_duration>] - [--channel_id=<channel_id>] [--channel_account_id=<channel_account_id>...] + [--channel_id=<channel_id>] [--channel_name=<channel_name>] + [--channel_account_id=<channel_account_id>...] [--account_id=<account_id>] [--claim_address=<claim_address>] [--preview] Options: @@ -2097,7 +2104,7 @@ class Daemon(metaclass=JSONRPCServerType): """ self.valid_stream_name_or_error(name) account = self.get_account_or_default(account_id) - channel = await self.get_channel_or_none(channel_account_id, channel_id, for_signing=True) + channel = await self.get_channel_or_none(channel_account_id, channel_id, channel_name, for_signing=True) amount = self.get_dewies_or_error('bid', bid, positive_value=True) claim_address = await self.get_receiving_address(claim_address, account) kwargs['fee_address'] = self.get_fee_address(kwargs, claim_address) @@ -2141,7 +2148,7 @@ class Daemon(metaclass=JSONRPCServerType): conditions=[WALLET_IS_UNLOCKED]) async def jsonrpc_stream_update( self, claim_id, bid=None, file_path=None, - channel_id=None, channel_account_id=None, clear_channel=False, + channel_id=None, channel_name=None, channel_account_id=None, clear_channel=False, account_id=None, claim_address=None, preview=False, **kwargs): """ @@ -2149,14 +2156,15 @@ class Daemon(metaclass=JSONRPCServerType): Usage: stream_update (<claim_id> | --claim_id=<claim_id>) - [--bid=<bid>] [--file_path=<file_path>] [--tags=<tags>...] [--clear-tags] + [--bid=<bid>] [--file_path=<file_path>] [--tags=<tags>...] [--clear_tags] [--fee_currency=<fee_currency>] [--fee_amount=<fee_amount>] [--fee_address=<fee_address>] [--title=<title>] [--description=<description>] [--author=<author>] [--language=<language>] [--license=<license>] [--license_url=<license_url>] [--thumbnail_url=<thumbnail_url>] [--release_time=<release_time>] [--stream_type=<stream_type>] [--video_width=<video_width>] [--video_height=<video_height>] [--video_duration=<video_duration>] [--image_width=<image_width>] [--image_height=<image_height>] [--audio_duration=<audio_duration>] - [--channel_id=<channel_id>] [--channel_account_id=<channel_account_id>...] [--clear-channel] + [--channel_id=<channel_id>] [--channel_name=<channel_name>] [--clear_channel] + [--channel_account_id=<channel_account_id>...] [--account_id=<account_id>] [--claim_address=<claim_address>] [--preview] Options: @@ -2164,7 +2172,7 @@ class Daemon(metaclass=JSONRPCServerType): --bid=<bid> : (decimal) amount to back the claim --file_path=<file_path> : (str) path to file to be associated with name. --tags=<tags> : (list) content tags - --clear-tags : (bool) clear existing tags (prior to adding new ones) + --clear_tags : (bool) clear existing tags (prior to adding new ones) --fee_currency=<fee_currency> : (string) specify fee currency --fee_amount=<fee_amount> : (decimal) content download fee --fee_address=<fee_address> : (str) address where to send fee payments, will use @@ -2191,7 +2199,7 @@ class Daemon(metaclass=JSONRPCServerType): --audio_duration=<duration> : (int) audio duration in seconds, an attempt will be made to calculate this automatically if not provided --channel_id=<channel_id> : (str) claim id of the publisher channel - --clear-channel : (bool) remove channel signature + --clear_channel : (bool) remove channel signature --channel_account_id=<channel_id>: (str) one or more account ids for accounts to look in for channel certificates, defaults to all accounts. --account_id=<account_id> : (str) account to use for funding the transaction @@ -2223,8 +2231,8 @@ class Daemon(metaclass=JSONRPCServerType): claim_address = old_txo.get_address(account.ledger) channel = None - if channel_id: - channel = await self.get_channel_or_error(channel_account_id, channel_id, for_signing=True) + if channel_id or channel_name: + channel = await self.get_channel_or_error(channel_account_id, channel_id, channel_name, for_signing=True) elif old_txo.claim.is_signed and not clear_channel: channel = old_txo.channel @@ -2974,22 +2982,28 @@ class Daemon(metaclass=JSONRPCServerType): self.valid_address_or_error(address) return address - async def get_channel_or_none(self, account_ids: List[str], channel_id: str = None, + async def get_channel_or_none(self, account_ids: List[str], channel_id: str = None, channel_name: str = None, for_signing: bool = False) -> Output: if channel_id is not None: - return await self.get_channel_or_error(account_ids, channel_id, for_signing) + return await self.get_channel_or_error(account_ids, channel_id, channel_name, for_signing) - async def get_channel_or_error(self, account_ids: List[str], channel_id: str = None, + async def get_channel_or_error(self, account_ids: List[str], channel_id: str = None, channel_name: str = None, for_signing: bool = False) -> Output: - if channel_id is None: - raise ValueError("Couldn't find channel because a channel_id was not provided.") + if channel_id: + key, value = 'id', channel_id + elif channel_name: + key, value = 'name', channel_name + else: + raise ValueError("Couldn't find channel because a channel_id or channel_name was not provided.") for account in self.get_accounts_or_all(account_ids): - channels = await account.get_channels(claim_id=channel_id, limit=1) - if channels: + channels = await account.get_channels(**{f'claim_{key}': value}, limit=1) + if len(channels) == 1: if for_signing and channels[0].private_key is None: - raise Exception(f"Couldn't find private key for channel '{channel_id}'. ") + raise Exception(f"Couldn't find private key for {key} '{value}'. ") return channels[0] - raise ValueError(f"Couldn't find channel with channel_id '{channel_id}'.") + elif len(channels) > 1: + raise ValueError(f"Multiple channels found with {key} '{value}', pass a channel_id to narrow it down.") + raise ValueError(f"Couldn't find channel with {key} '{value}'.") def get_account_or_default(self, account_id: str, argument_name: str = "account", lbc_only=True) -> LBCAccount: if account_id is None: diff --git a/tests/integration/test_claim_commands.py b/tests/integration/test_claim_commands.py index e0f986606..d8ba10da5 100644 --- a/tests/integration/test_claim_commands.py +++ b/tests/integration/test_claim_commands.py @@ -1,4 +1,6 @@ import hashlib +import tempfile +import logging from binascii import unhexlify import base64 @@ -246,8 +248,10 @@ class StreamCommands(CommandTestCase): # defaults to using all accounts to lookup channel await self.stream_create('hovercraft1', channel_id=baz_id) + self.assertEqual((await self.claim_search('hovercraft1'))[0]['channel_name'], '@baz') # uses only the specific accounts which contains the channel await self.stream_create('hovercraft2', channel_id=baz_id, channel_account_id=[account2_id]) + self.assertEqual((await self.claim_search('hovercraft2'))[0]['channel_name'], '@baz') # fails when specifying account which does not contain channel with self.assertRaisesRegex(ValueError, "Couldn't find channel with channel_id"): await self.stream_create( @@ -386,6 +390,45 @@ class StreamCommands(CommandTestCase): await self.claim_abandon(tx['outputs'][0]['claim_id']) await self.assertBalance(self.account, '9.97968399') + async def test_publish(self): + + # errors on missing arguments to create a stream + with self.assertRaisesRegex(Exception, "'bid' is a required argument for new publishes."): + await self.daemon.jsonrpc_publish('foo') + + with self.assertRaisesRegex(Exception, "'file_path' is a required argument for new publishes."): + await self.daemon.jsonrpc_publish('foo', bid='1.0') + + # successfully create stream + with tempfile.NamedTemporaryFile() as file: + file.write(b'hi') + file.flush() + tx1 = await self.publish('foo', bid='1.0', file_path=file.name) + + # doesn't error on missing arguments when doing an update stream + tx2 = await self.publish('foo', tags='updated') + self.assertEqual( + tx1['outputs'][0]['claim_id'], + tx2['outputs'][0]['claim_id'] + ) + + # update conflict with two claims of the same name + tx3 = await self.stream_create('foo', allow_duplicate_name=True) + with self.assertRaisesRegex(Exception, "There are 2 claims for 'foo'"): + await self.daemon.jsonrpc_publish('foo') + + # abandon duplicate channel + await self.claim_abandon(tx3['outputs'][0]['claim_id']) + + # publish to a channel + await self.channel_create('@abc') + tx3 = await self.publish('foo', channel_name='@abc') + r = await self.resolve('lbry://@abc/foo') + self.assertEqual( + r['lbry://@abc/foo']['claim']['claim_id'], + tx3['outputs'][0]['claim_id'] + ) + async def test_claim_search(self): # search for channel claim channel = await self.channel_create('@abc', '1.0') diff --git a/tests/integration/testcase.py b/tests/integration/testcase.py index c163a7d37..e45b8357b 100644 --- a/tests/integration/testcase.py +++ b/tests/integration/testcase.py @@ -186,6 +186,15 @@ class CommandTestCase(IntegrationTestCase): await self.on_transaction_dict(claim) return claim + async def publish(self, name, *args, confirm=True, **kwargs): + claim = await self.out(self.daemon.jsonrpc_publish(name, *args, **kwargs)) + self.assertEqual(claim['outputs'][0]['name'], name) + if confirm: + await self.on_transaction_dict(claim) + await self.generate(1) + await self.on_transaction_dict(claim) + return claim + async def channel_create(self, name='@arena', bid='1.0', confirm=True, **kwargs): channel = await self.out(self.daemon.jsonrpc_channel_create(name, bid, **kwargs)) self.assertEqual(channel['outputs'][0]['name'], name)