def sync_vote(request): trx_id = request.GET.get("trx_id") try: # block numbers must be integer block_num = int(request.GET.get("block_num")) except (TypeError, ValueError): return HttpResponse('Invalid block ID', status=400) c = LightsteemClient(nodes=["https://api.hive.blog"]) block_data = c.get_block(block_num) if not block_data: # block data may return null if it's invalid return HttpResponse('Invalid block ID', status=400) vote_tx = None for transaction in block_data.get("transactions", []): if transaction.get("transaction_id") == trx_id: vote_tx = transaction break if not vote_tx: return HttpResponse('Invalid transaction ID', status=400) vote_op = None for op_type, op_value in transaction.get("operations", []): if op_type != "comment": continue vote_op = op_value if not vote_op: return HttpResponse("Couldn't find valid vote operation.", status=400) # validate json metadata if not vote_op.get("json_metadata"): return HttpResponse("json_metadata is missing.", status=400) json_metadata = json.loads(vote_op.get("json_metadata", "")) # json_metadata should indicate content type if json_metadata.get("content_type") != "poll_vote": return HttpResponse("content_type field is missing.", status=400) # check votes votes = json_metadata.get("votes", []) if not len(votes): return HttpResponse("votes field is missing.", status=400) # check the poll exists try: question = Question.objects.get( username=vote_op.get("parent_author"), permlink=vote_op.get("parent_permlink"), ) except Question.DoesNotExist: return HttpResponse("parent_author/parent_permlink is not a poll.", status=400) # Validate the choice choices = Choice.objects.filter(question=question, ) selected_choices = [] for choice in choices: for user_vote in votes: if choice.text == user_vote: selected_choices.append(choice) if not selected_choices: return HttpResponse("Invalid choices in votes field.", status=400) # check if the user exists in our database # if it doesn't, create it. try: user = User.objects.get(username=vote_op.get("author")) except User.DoesNotExist: user = User.objects.create_user(username=vote_op.get("author")) user.save() # check if we already registered a vote from that user if Choice.objects.filter(voted_users__username=vote_op.get("author"), question=question).count() != 0: return HttpResponse("You have already voted on that poll.", status=400) # register the vote for selected_choice in selected_choices: selected_choice.voted_users.add(user) # add vote audit entry vote_audit = VoteAudit(question=question, voter=user, block_id=block_num, trx_id=trx_id) vote_audit.save() return HttpResponse("Vote is registered to the database.", status=200)
class TestClient(unittest.TestCase): NODES = ["https://api.steemit.com"] def setUp(self): self.client = Client(nodes=TestClient.NODES) def test_dynamic_api_selection(self): self.client('tags_api') self.assertEqual('tags_api', self.client.api_type) def test_default_api_selection(self): with requests_mock.mock() as m: m.post(TestClient.NODES[0], json={"result": {}}) self.client.get_block(12323) self.assertEqual('condenser_api', self.client.api_type) def test_get_rpc_request_body_condenser_multiple_args(self): self.client('condenser_api') rpc_body = self.client.get_rpc_request_body( ('get_account_bandwidth', 'steemit', 'forum'), { 'batch': True, 'id': 1 }) self.assertEqual( "condenser_api.get_account_bandwidth", rpc_body["method"], ) self.assertEqual( ('steemit', 'forum'), rpc_body["params"], ) def test_get_rpc_request_body_condenser_single_arg(self): self.client('condenser_api') rpc_body = self.client.get_rpc_request_body( ('get_block', '123'), {}, ) self.assertEqual( ('123', ), rpc_body["params"], ) def test_get_rpc_request_body_non_condenser_api_with_arg(self): self.client('database_api') rpc_body = self.client.get_rpc_request_body( ('list_vesting_delegations', { "start": [None], "limit": 20, "order": "by_delegation" }), {}, ) self.assertEqual( { 'start': [None], 'limit': 20, 'order': 'by_delegation' }, rpc_body["params"]) def test_get_rpc_request_body_non_condenser_api_no_arg(self): self.client('database_api') rpc_body = self.client.get_rpc_request_body( ('get_active_witnesses', ), {}, ) self.assertEqual({}, rpc_body["params"]) def test_get_rpc_request_body_condenser_api_no_arg(self): rpc_body = self.client.get_rpc_request_body( ('get_active_witnesses', ), {}, ) self.assertEqual([], rpc_body["params"]) def test_batch_rpc_calls(self): self.client.get_block(1, batch=True) self.client.get_block_header(2, batch=True) self.assertEqual(2, len(self.client.queue)) self.assertEqual("condenser_api.get_block", self.client.queue[0]["method"]) self.assertEqual("condenser_api.get_block_header", self.client.queue[1]["method"]) def test_validate_response_rpc_error(self): resp = { 'jsonrpc': '2.0', 'error': { 'code': -32000, 'message': "Parse Error:Couldn't parse uint64_t", 'data': "" }, 'id': 'f0acccf6-ebf6-4952-97da-89b248dfb0d0' } with self.assertRaises(lightsteem.exceptions.RPCNodeException): self.client.validate_response(resp) def test_validate_repsonse_batch_call(self): resp = [{ 'previous': '017d08b4416e4ea77d5f582ddf4fc06bcf888eef', 'timestamp': '2018-08-11T10:25:00', 'witness': 'thecryptodrive', 'transaction_merkle_root': '23676c4bdc0074489392892bcf' '1a5b779f280c8e', 'extensions': [] }, { 'previous': '017d08b55aa2520bc3a777eaec77e872bb6b8943', 'timestamp': '2018-08-11T10:25:03', 'witness': 'drakos', 'transaction_merkle_root': 'a4be1913157a1be7e4ab' 'c36a22ffde1c110e683c', 'extensions': [] }] validated_resp = self.client.validate_response(resp) self.assertEqual(True, isinstance(validated_resp, list)) self.assertEqual(2, len(validated_resp)) def test_validate_repsonse_batch_call_one_error_one_fail(self): resp = [{ 'previous': '017d08b4416e4ea77d5f582ddf4fc06bcf888eef', 'timestamp': '2018-08-11T10:25:00', 'witness': 'thecryptodrive', 'transaction_merkle_root': '23676c4bdc0074489392892bcf' '1a5b779f280c8e', 'extensions': [] }, { 'jsonrpc': '2.0', 'error': { 'code': -32000, 'message': "Parse Error:Couldn't parse uint64_t", 'data': "" }, 'id': 'f0acccf6-ebf6-4952-97da-89b248dfb0d0' }] with self.assertRaises(lightsteem.exceptions.RPCNodeException): self.client.validate_response(resp) def test_process_batch(self): with requests_mock.mock() as m: m.post(TestClient.NODES[0], json={"result": {}}) self.client.get_block(12323, batch=True) self.client.get_block(1234, batch=True) self.assertEqual(2, len(self.client.queue)) self.client.process_batch() self.assertEqual(0, len(self.client.queue))