A standalone test case for the API thread #1900

Closed
g1itch wants to merge 3 commits from api-standalone into v0.6
3 changed files with 112 additions and 46 deletions

View File

@ -57,28 +57,25 @@ For further examples please reference `.tests.test_api`.
"""
import base64
import ConfigParser
import errno
import hashlib
import httplib
import json
import random # nosec
import socket
import subprocess
import time
import xmlrpclib
from binascii import hexlify, unhexlify
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from struct import pack
from six.moves import configparser, http_client, xmlrpc_server
import defaults
import helper_inbox
import helper_sent
import network.stats
import proofofwork
import queues
import shared
import shutdown
import state
from addresses import (
addBMIfNotPresent,
@ -90,9 +87,21 @@ from addresses import (
from bmconfigparser import BMConfigParser
from debug import logger
from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready
from inventory import Inventory
# TODO: solve the issue with next two imports and remove the cutout
# FIXME: perhaps this module shouldn't use the Inventory at all
try:
import shutdown
from inventory import Inventory
except ImportError:
Inventory = None
try:
import network.stats as network_stats
except ImportError:
network_stats = None
from network.threads import StoppableThread
from six.moves import queue
from version import softwareVersion
try: # TODO: write tests for XML vulnerabilities
@ -162,7 +171,7 @@ class ErrorCodes(type):
return result
class APIError(xmlrpclib.Fault):
class APIError(xmlrpc_server.Fault):
"""
APIError exception class
@ -210,7 +219,7 @@ class singleAPI(StoppableThread):
except AttributeError:
errno.WSAEADDRINUSE = errno.EADDRINUSE
RPCServerBase = SimpleXMLRPCServer
RPCServerBase = xmlrpc_server.SimpleXMLRPCServer
ct = 'text/xml'
if BMConfigParser().safeGet(
'bitmessagesettings', 'apivariant') == 'json':
@ -351,7 +360,7 @@ class command(object): # pylint: disable=too-few-public-methods
# Modified by Jonathan Warren (Atheros).
# Further modified by the Bitmessage developers
# http://code.activestate.com/recipes/501148
class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler):
"""The main API handler"""
# pylint: disable=protected-access
@ -392,7 +401,7 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
validuser = self.APIAuthenticateClient()
if not validuser:
time.sleep(2)
self.send_response(httplib.UNAUTHORIZED)
self.send_response(http_client.UNAUTHORIZED)
self.end_headers()
return
# "RPC Username or password incorrect or HTTP header"
@ -409,11 +418,11 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
)
except Exception: # This should only happen if the module is buggy
# internal error, report as HTTP server error
self.send_response(httplib.INTERNAL_SERVER_ERROR)
self.send_response(http_client.INTERNAL_SERVER_ERROR)
self.end_headers()
else:
# got a valid XML RPC response
self.send_response(httplib.OK)
self.send_response(http_client.OK)
self.send_header("Content-type", self.server.content_type)
self.send_header("Content-length", str(len(response)))
@ -858,7 +867,7 @@ class BMRPCDispatcher(object):
' Use deleteAddress API call instead.')
try:
self.config.remove_section(address)
except ConfigParser.NoSectionError:
except configparser.NoSectionError:
raise APIError(
13, 'Could not find this address in your keys.dat file.')
self.config.save()
@ -875,7 +884,7 @@ class BMRPCDispatcher(object):
address = addBMIfNotPresent(address)
try:
self.config.remove_section(address)
except ConfigParser.NoSectionError:
except configparser.NoSectionError:
raise APIError(
13, 'Could not find this address in your keys.dat file.')
self.config.save()
@ -1266,6 +1275,8 @@ class BMRPCDispatcher(object):
requiredPayloadLengthExtraBytes):
"""Handle a request to disseminate an encrypted message"""
if Inventory is None:
raise APIError(21, 'Could not import Inventory.')
# The device issuing this command to PyBitmessage supplies a msg
# object that has already been encrypted but which still needs the POW
# to be done. PyBitmessage accepts this msg object and sends it out
@ -1322,6 +1333,8 @@ class BMRPCDispatcher(object):
def HandleDissimatePubKey(self, payload):
"""Handle a request to disseminate a public key"""
if Inventory is None:
raise APIError(21, 'Could not import Inventory.')
# The device issuing this command to PyBitmessage supplies a pubkey
# object to be disseminated to the rest of the Bitmessage network.
# PyBitmessage accepts this pubkey object and sends it out to the rest
@ -1411,7 +1424,10 @@ class BMRPCDispatcher(object):
or "connectedAndReceivingIncomingConnections".
"""
connections_num = len(network.stats.connectedHostsList())
try:
connections_num = len(network_stats.connectedHostsList())
except AttributeError:
raise APIError(21, 'Could not import network_stats.')
if connections_num == 0:
networkStatus = 'notConnected'
elif state.clientHasReceivedIncomingConnections:
@ -1423,7 +1439,7 @@ class BMRPCDispatcher(object):
'numberOfMessagesProcessed': state.numberOfMessagesProcessed,
'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed,
'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed,
'pendingDownload': network.stats.pendingDownload(),
'pendingDownload': network_stats.pendingDownload(),
'networkStatus': networkStatus,
'softwareName': 'PyBitmessage',
'softwareVersion': softwareVersion
@ -1439,25 +1455,11 @@ class BMRPCDispatcher(object):
"""Test two numeric params"""
return a + b
@testmode('clearUISignalQueue')
def HandleclearUISignalQueue(self):
"""clear UISignalQueue"""
queues.UISignalQueue.queue.clear()
return "success"
@command('statusBar')
def HandleStatusBar(self, message):
"""Update GUI statusbar message"""
queues.UISignalQueue.put(('updateStatusBar', message))
@testmode('getStatusBar')
def HandleGetStatusBar(self):
"""Get GUI statusbar message"""
try:
_, data = queues.UISignalQueue.get(block=False)
except queue.Empty:
return None
return data
return "success"
@testmode('undeleteMessage')
def HandleUndeleteMessage(self, msgid):
@ -1475,6 +1477,8 @@ class BMRPCDispatcher(object):
@command('shutdown')
def HandleShutdown(self):
"""Shutdown the bitmessage. Returns 'done'."""
if Inventory is None:
raise APIError(21, 'Could not import shutdown.')
# backward compatible trick because False == 0 is True
state.shutdown = False
return 'done'

View File

@ -12,7 +12,7 @@ from six.moves import xmlrpc_client # nosec
import psutil
from .samples import (
sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, sample_statusbar_msg,
sample_seed, sample_deterministic_addr3, sample_deterministic_addr4,
sample_inbox_msg_ids, sample_test_subscription_address, sample_subscription_name)
from .test_process import TestProcessProto
@ -86,18 +86,6 @@ class TestAPI(TestAPIProto):
'API Error 0020: Invalid method: test'
)
def test_statusbar_method(self):
"""Test statusbar method"""
self.api.clearUISignalQueue()
self.assertEqual(
self.api.statusBar(sample_statusbar_msg),
'null'
)
self.assertEqual(
self.api.getStatusBar(),
sample_statusbar_msg
)
def test_message_inbox(self):
"""Test message inbox methods"""
self.assertEqual(

View File

@ -0,0 +1,74 @@
import time
import unittest
from six.moves import queue, xmlrpc_client
from pybitmessage import pathmagic
from .samples import sample_statusbar_msg # any
class TestAPIThread(unittest.TestCase):
"""Test case running the API thread"""
@classmethod
def setUpClass(cls):
pathmagic.setup() # need this because of import state in network ):
import helper_sql
import helper_startup
import queues
import state
from bmconfigparser import BMConfigParser
# pylint: disable=too-few-public-methods
class SqlReadyMock(object):
"""Mock helper_sql.sql_ready event with dummy class"""
@staticmethod
def wait():
"""Don't wait, return immediately"""
return
helper_sql.sql_ready = SqlReadyMock
cls.state = state
cls.queues = queues
helper_startup.loadConfig()
# helper_startup.fixSocket()
config = BMConfigParser()
config.set(
'bitmessagesettings', 'apiusername', 'username')
config.set(
'bitmessagesettings', 'apipassword', 'password')
config.save()
import api
cls.thread = api.singleAPI()
cls.thread.daemon = True
cls.thread.start()
time.sleep(3)
cls.api = xmlrpc_client.ServerProxy(
"http://username:password@127.0.0.1:8442/")
def test_connection(self):
"""API command 'helloWorld'"""
self.assertEqual(
self.api.helloWorld('hello', 'world'), 'hello-world')
def test_statusbar(self):
"""Check UISignalQueue after issuing the 'statusBar' command"""
self.queues.UISignalQueue.queue.clear()
self.assertEqual(
self.api.statusBar(sample_statusbar_msg), 'success')
try:
cmd, data = self.queues.UISignalQueue.get(block=False)
except queue.Empty:
self.fail('UISignalQueue is empty!')
self.assertEqual(cmd, 'updateStatusBar')
self.assertEqual(data, sample_statusbar_msg)
@classmethod
def tearDownClass(cls):
cls.state.shutdown = 1