import logging import typing import random import asyncio from aiohttp import web from lbrynet.testcase import CommandTestCase import lbrynet.schema lbrynet.schema.BLOCKCHAIN_NAME = 'lbrycrd_regtest' class FakedCommentServer: ERRORS = { 'INVALID_URI': {'code': 1, 'message': 'Invalid claim URI'}, 'INVALID_PARAMS': {'code': -32602, 'message': 'Invalid parameters'}, 'INTERNAL': {'code': -32603, 'message': 'An internal error'}, 'UNKNOWN': {'code': -1, 'message': 'An unknown or very miscellaneous error'}, } def __init__(self, port=2903): self.port = port self.app = web.Application(debug=True) self.app.add_routes([web.post('/api', self.api)]) self.runner = None self.server = None def get_claim_comments(self, uri: str, better_keys: bool) -> typing.Union[dict, list, None]: if not uri.startswith('lbry://'): # Very basic error case return {'error': self.ERRORS['INVALID_URI']} return [self.get_comment(i) for i in range(75)] def get_comment(self, comment_id: int, parent_id: int = None) -> dict: return { 'comment_id': comment_id, 'parent_id': parent_id, 'author': f'Person{comment_id}', 'message': f'comment {comment_id}', 'claim_id': random.randint(1, 2**16), 'time_posted': random.randint(2**16, 2**32 - 1), 'upvotes': random.randint(0, 9999), 'downvotes': random.randint(0, 9999) } def comment(self, uri: str, poster: str, message: str) -> typing.Union[int, dict, None]: if not uri.startswith('lbry://'): return {'error': self.ERRORS['INVALID_URI']} return random.randint(1, 9999) def reply(self, parent_id: int, poster: str, message: str) -> dict: if 2 <= len(message) <= 2000 and 2 <= len(poster) <= 127 and parent_id > 0: return random.randint(parent_id + 1, 2**32 - 1) return {'error': self.ERRORS['INVALID_PARAMS']} def get_comment_data(self, comm_index: int, better_keys: bool = False) -> typing.Union[dict, None]: return self.get_comment(comm_index) def get_comment_replies(self, comm_index: int) -> typing.Union[list, None]: return [random.randint(comm_index, comm_index+250) for _ in range(75)] methods = { 'get_claim_comments': get_claim_comments, 'get_comment_data': get_comment_data, 'get_comment_replies': get_comment_replies, 'comment': comment, 'reply': reply } def process_json(self, body) -> dict: response = {'jsonrpc': '2.0', 'id': body['id']} if body['method'] in self.methods: params = body.get('params', {}) result = self.methods[body['method']](self, **params) if type(result) is dict and 'error' in result: response['error'] = result['error'] else: response['result'] = result else: response['error'] = self.ERRORS['UNKNOWN'] return response async def _start(self): self.runner = web.AppRunner(self.app) await self.runner.setup() self.server = web.TCPSite(self.runner, 'localhost', self.port) await self.server.start() async def _stop(self): await self.runner.cleanup() async def run(self, max_timeout=3600): try: await self._start() await asyncio.sleep(max_timeout) except asyncio.CancelledError: pass finally: await self._stop() async def api(self, request): body = await request.json() if type(body) is list or type(body) is dict: if type(body) is list: response = [self.process_json(part) for part in body] else: response = self.process_json(body) return web.json_response(response) else: return web.json_response({'error': self.ERRORS['UNKNOWN']}) class CommentCommands(CommandTestCase): VERBOSITY = logging.WARN async def asyncSetUp(self): await super().asyncSetUp() self.daemon.conf.comment_server = 'http://localhost:2903/api' self.server = FakedCommentServer(2903) self.server_task = asyncio.create_task(self.server.run(self.timeout)) async def asyncTearDown(self): await super().asyncTearDown() self.server_task.cancel() if not self.server_task.cancelled(): await self.server_task async def test_comment_create(self): claim = await self.stream_create(name='doge', bid='0.001', data=b'loool') self.assertIn('outputs', claim) comment = await self.daemon.jsonrpc_comment_create( claim_id=claim['outputs'][0]['claim_id'], channel_id='Jimmy Buffett', message="It's 5 O'Clock Somewhere" ) self.assertIs(type(comment), dict, msg=f"Response type ({type(comment)})is not dict: {comment}") self.assertIn('message', comment, msg=f"Response {comment} doesn't contain message") self.assertIn('author', comment) async def test_comment_create_reply(self): claim = await self.stream_create(name='doge', bid='0.001') self.assertIn('outputs', claim) reply = await self.daemon.jsonrpc_comment_create( claim_id=claim['outputs'][0]['claim_id'], channel_id='Jimmy Buffett', message='Let\'s all go to Margaritaville', parent_comment_id=42 ) self.assertIs(type(reply), dict, msg=f'Response {type(reply)} is not dict\nResponse: {reply}') self.assertIn('author', reply) async def test_comment_list_root_level(self): claim = await self.stream_create(name='doge', bid='0.001') self.assertIn('outputs', claim) claim_id = claim['outputs'][0]['claim_id'] comments = await self.daemon.jsonrpc_comment_list(claim_id) self.assertIsNotNone(type(comments)) self.assertIs(type(comments), dict) self.assertIn('comments', comments, f"'comments' field was not found in returned dict: {comments}") self.assertIs(type(comments['comments']), list, msg=f'comment_list: {comments}') comments = await self.daemon.jsonrpc_comment_list(claim_id, page_size=50) self.assertIsNotNone(comments) self.assertIs(type(comments), dict) self.assertIn('comments', comments, f"'comments' field was not found in returned dict: {comments}") comment_list = comments['comments'] self.assertEqual(len(comment_list), 50, msg=f'comment_list incorrect size {len(comment_list)}: {comment_list}') comments = await self.daemon.jsonrpc_comment_list(claim_id, page_size=50, page=2) self.assertEqual(len(comments['comments']), 25, msg=f'comment list page 2: {comments["comments"]}') comments = await self.daemon.jsonrpc_comment_list(claim_id, page_size=50, page=3) self.assertEqual(len(comments['comments']), 0, msg=f'comment list is non-zero: {comments["comments"]}') async def test_comment_list_replies(self): claim = await self.stream_create(name='doge', bid='0.001') self.assertIn('outputs', claim) claim_id = claim['outputs'][0]['claim_id'] replies = await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=23) self.assertIsInstance(replies['comments'], list, msg=f'Invalid type: {replies["comments"]} should be list') self.assertGreater(len(replies['comments']), 0, msg='Returned replies are empty') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=25, page_size=50))['comments'] self.assertEqual(len(replies), 50, f'Replies invalid length ({len(replies)})') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=67, page_size=23, page=5))['comments'] self.assertEqual(len(replies), 0, f'replies {replies} not 23: {len(replies)}') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=79, page_size=60, page=2))['comments'] self.assertEqual(len(replies), 15, f'Size of replies is incorrect, should be 15: {replies}') async def test_comment_list_flatness_flatness_LA(self): claim = await self.stream_create(name='doge', bid='0.001') self.assertIn('outputs', claim) claim_id = claim['outputs'][0]['claim_id'] replies = await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=23, flat=True) self.assertIsInstance(replies['comments'], list, msg=f'Invalid type: {replies["comments"]} should be list') self.assertGreater(len(replies['comments']), 0, msg='Returned replies are empty') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=25, flat=True, max_replies_shown=0, page_size=50))['comments'] self.assertEqual(len(replies), 50, f'Replies invalid length ({len(replies)})') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=67, flat=True, page_size=23, page=5))['comments'] self.assertEqual(len(replies), 0, f'replies {replies} not 23: {len(replies)}') replies = (await self.daemon.jsonrpc_comment_list(claim_id, parent_comment_id=79, page_size=60, page=2))['comments'] self.assertGreaterEqual(len(replies), 15, f'Size of replies is incorrect, should be 15: {replies}')