예제 #1
0
class StompRequestHandler(BaseRequestHandler, StompConnection):
    """
    Class that will be instantiated to handle STOMP connections.

    This class will be instantiated once per connection to the server.  In a multi-threaded
    context, that means that instances of this class are scoped to a single thread.  It should
    be noted that while the L{coilmq.engine.StompEngine} instance will be thread-local, the 
    storage containers configured into the engine are not thread-local (and hence must be
    thread-safe). 
    
    @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with
                    complete STOMP messages.
    @type buffer: C{stompclient.util.FrameBuffer}
    
    @ivar engine: The STOMP protocol engine.
    @type engine: L{coilmq.engine.StompEngine}
    
    @ivar debug: Whether to enable extra-verbose debug logging.  (Will be logged at debug level.)
    @type debug: C{bool}
    """
    
    def setup(self):
        if self.server.timeout is not None:
            self.request.settimeout(self.server.timeout)
        self.debug = False
        self.log = logging.getLogger('%s.%s' % (self.__module__, self.__class__.__name__))
        self.buffer = FrameBuffer()
        self.engine = StompEngine(connection=self,
                                  authenticator=self.server.authenticator,
                                  queue_manager=self.server.queue_manager,
                                  topic_manager=self.server.topic_manager)
        
    def handle(self):
        """
        Handle a new socket connection.
        """
        # self.request is the TCP socket connected to the client
        try:
            while not self.server._shutdown_request_event.is_set():
                try:
                    data = self.request.recv(8192)
                    if not data:
                        break
                    if self.debug:
                        self.log.debug("RECV: %r" % data)
                    self.buffer.append(data)
                    
                    for frame in self.buffer:
                        self.log.debug("Processing frame: %s" % frame)
                        self.engine.process_frame(frame)
                        if not self.engine.connected:
                            raise ClientDisconnected()
                except socket.timeout:
                    pass
        except ClientDisconnected:
            self.log.debug("Client disconnected, discontinuing read loop.")
        except Exception, e:
            self.log.error("Error receiving data (unbinding): %s" % e)
            self.engine.unbind()
            raise
예제 #2
0
 def setUp(self):
     self.qm = MockQueueManager()
     self.tm = MockTopicManager()
     self.conn = MockConnection()
     self.auth = MockAuthenticator()
     self.engine = StompEngine(connection=self.conn,
                               queue_manager=self.qm,
                               topic_manager=self.tm,
                               authenticator=None)
예제 #3
0
 def setup(self):
     self.debug = False
     self.log = logging.getLogger(
         '%s.%s' % (self.__module__, self.__class__.__name__))
     self.buffer = StompFrameBuffer()
     self.engine = StompEngine(connection=self,
                               authenticator=self.server.authenticator,
                               queue_manager=self.server.queue_manager,
                               topic_manager=self.server.topic_manager)
예제 #4
0
class StompProtocol(Protocol, StompConnection):
    """
    Subclass of C{twisted.internet.protocol.Protocol} for handling STOMP communications.
    
    An instance of this class will be created for each connecting client. 
    
    @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with
                    complete STOMP messages.
    @type buffer: C{stomper.stompbuffer.StompBuffer}
    
    @ivar engine: The STOMP protocol engine.
    @type engine: L{coilmq.engine.StompEngine}
    """
    def __init__(self, queue_manager, topic_manager):
        self.log = logging.getLogger(
            '%s.%s' % (self.__class__.__module__, self.__class__.__name__))
        self.log.debug("Initializing StompProtocol.")
        self.buffer = StompFrameBuffer()
        self.engine = StompEngine(
            connection=self,
            authenticator=None,  # FIXME: Add the authenticator
            queue_manager=queue_manager,
            topic_manager=topic_manager)

    def connectionLost(self, reason):
        Protocol.connectionLost(self, reason)
        self.log.debug("Connection lost.")
        self.engine.unbind()

    def connectionMade(self):
        self.log.debug("Connection made.")

    def dataReceived(self, data):
        """ Twisted calls this method when data is received.
         
        Note: The data may not be not be a complete frame or may be more than
        one frame.
        """
        self.log.debug("Data received: %s" % data)
        self.buffer.append(data)

        # print '%r' % self.buffer

        for frame in self.buffer:
            self.log.debug("Processing frame: %s" % frame)
            self.engine.process_frame(frame)

    def send_frame(self, frame):
        """ Sends a frame to connected socket client.
        
        (Sorry, Twisted, our code is PEP-8.)
        
        @param frame: The frame to send.
        @type frame: L{coilmq.frame.StompFrame}
        """
        self.transport.write(frame.pack())
예제 #5
0
 def __init__(self, queue_manager, topic_manager):
     self.log = logging.getLogger(
         '%s.%s' % (self.__class__.__module__, self.__class__.__name__))
     self.log.debug("Initializing StompProtocol.")
     self.buffer = StompFrameBuffer()
     self.engine = StompEngine(
         connection=self,
         authenticator=None,  # FIXME: Add the authenticator
         queue_manager=queue_manager,
         topic_manager=topic_manager)
예제 #6
0
class StompProtocol(Protocol, StompConnection):
    """
    Subclass of C{twisted.internet.protocol.Protocol} for handling STOMP communications.
    
    An instance of this class will be created for each connecting client. 
    
    @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with
                    complete STOMP messages.
    @type buffer: C{stomper.stompbuffer.StompBuffer}
    
    @ivar engine: The STOMP protocol engine.
    @type engine: L{coilmq.engine.StompEngine}
    """
    def __init__(self, queue_manager, topic_manager):
        self.log = logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__name__))
        self.log.debug("Initializing StompProtocol.")
        self.buffer = StompFrameBuffer()
        self.engine = StompEngine(connection=self,
                                  authenticator=None, # FIXME: Add the authenticator
                                  queue_manager=queue_manager,
                                  topic_manager=topic_manager) 

    def connectionLost(self, reason):
        Protocol.connectionLost(self, reason)
        self.log.debug("Connection lost.")
        self.engine.unbind()

    def connectionMade(self):
        self.log.debug("Connection made.")

    def dataReceived(self, data):
        """ Twisted calls this method when data is received.
         
        Note: The data may not be not be a complete frame or may be more than
        one frame.
        """
        self.log.debug("Data received: %s" % data)
        self.buffer.append(data)
        
        # print '%r' % self.buffer
        
        for frame in self.buffer:
            self.log.debug("Processing frame: %s" % frame)
            self.engine.process_frame(frame)
    
    def send_frame(self, frame):
        """ Sends a frame to connected socket client.
        
        (Sorry, Twisted, our code is PEP-8.)
        
        @param frame: The frame to send.
        @type frame: L{coilmq.frame.StompFrame}
        """
        self.transport.write(frame.pack())
예제 #7
0
 def setup(self):
     if self.server.timeout is not None:
         self.request.settimeout(self.server.timeout)
     self.debug = False
     self.log = logging.getLogger(
         '%s.%s' % (self.__module__, self.__class__.__name__))
     self.buffer = FrameBuffer()
     self.engine = StompEngine(connection=self,
                               authenticator=self.server.authenticator,
                               queue_manager=self.server.queue_manager,
                               topic_manager=self.server.topic_manager,
                               protocol=self.server.protocol)
 def setup(self):
     self.debug = False
     self.log = logging.getLogger('%s.%s' % (self.__module__, self.__class__.__name__))
     self.buffer = StompFrameBuffer()
     self.engine = StompEngine(connection=self,
                               authenticator=self.server.authenticator,
                               queue_manager=self.server.queue_manager,
                               topic_manager=self.server.topic_manager)
예제 #9
0
 def __init__(self, queue_manager, topic_manager):
     self.log = logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__name__))
     self.log.debug("Initializing StompProtocol.")
     self.buffer = StompFrameBuffer()
     self.engine = StompEngine(connection=self,
                               authenticator=None, # FIXME: Add the authenticator
                               queue_manager=queue_manager,
                               topic_manager=topic_manager) 
예제 #10
0
 def setUp(self):
     self.qm = MockQueueManager()
     self.tm = MockTopicManager()
     self.conn = MockConnection()
     self.auth = MockAuthenticator()
     self.engine = StompEngine(connection=self.conn,
                               queue_manager=self.qm,
                               topic_manager=self.tm,
                               authenticator=None)
예제 #11
0
 def setup(self):
     if self.server.timeout is not None:
         self.request.settimeout(self.server.timeout)
     self.debug = False
     self.log = logging.getLogger('%s.%s' % (self.__module__, self.__class__.__name__))
     self.buffer = FrameBuffer()
     self.engine = StompEngine(connection=self,
                               authenticator=self.server.authenticator,
                               queue_manager=self.server.queue_manager,
                               topic_manager=self.server.topic_manager,
                               protocol=self.server.protocol)
예제 #12
0
class ProtocolBaseTestCase(unittest.TestCase):
    def get_protocol(self):
        return STOMP12

    def setUp(self):
        self.qm = MockQueueManager()
        self.tm = MockTopicManager()
        self.conn = MockConnection()
        self.auth = MockAuthenticator()
        self.engine = StompEngine(connection=self.conn,
                                  queue_manager=self.qm,
                                  topic_manager=self.tm,
                                  authenticator=None,
                                  protocol=self.get_protocol())

    def feed_frame(self, cmd, headers=None, body=''):
        self.engine.process_frame(Frame(cmd, headers or {}, body))
        return self.conn.frames[-1]

    def tearDown(self):
        self.conn.reset()
예제 #13
0
파일: __init__.py 프로젝트: hozn/coilmq
class ProtocolBaseTestCase(unittest.TestCase):

    def get_protocol(self):
        return STOMP12

    def setUp(self):
        self.qm = MockQueueManager()
        self.tm = MockTopicManager()
        self.conn = MockConnection()
        self.auth = MockAuthenticator()
        self.engine = StompEngine(connection=self.conn,
                                  queue_manager=self.qm,
                                  topic_manager=self.tm,
                                  authenticator=None,
                                  protocol=self.get_protocol())

    def feed_frame(self, cmd, headers=None, body=''):
        self.engine.process_frame(Frame(cmd, headers or {}, body))
        return self.conn.frames[-1]

    def tearDown(self):
        self.conn.reset()
예제 #14
0
class EngineTest(unittest.TestCase):
    def setUp(self):
        self.qm = MockQueueManager()
        self.tm = MockTopicManager()
        self.conn = MockConnection()
        self.auth = MockAuthenticator()
        self.engine = StompEngine(connection=self.conn,
                                  queue_manager=self.qm,
                                  topic_manager=self.tm,
                                  authenticator=None)

    def tearDown(self):
        self.conn.reset()

    def _connect(self):
        """ Call the engine connect() method so that we have a valid 'session'. """
        self.engine.connect(Frame('CONNECT'))

    def assertErrorFrame(self, frame, msgsub):
        """ Assert that the passed in frame is an error frame and that message contains specified
        string.
        """
        assert frame.command == 'ERROR'
        assert msgsub.lower() in frame.headers['message'].lower()

    def test_connect_no_auth(self):
        """ Test the CONNECT command with no auth required. """

        assert self.engine.connected == False
        self.engine.process_frame(Frame('CONNECT'))
        assert self.engine.connected == True

    def test_connect_auth(self):
        """ Test the CONNECT command when auth is required. """
        self.engine.authenticator = self.auth

        assert self.engine.connected == False
        self.engine.process_frame(Frame('CONNECT'))
        self.assertErrorFrame(self.conn.frames[-1], 'Auth')
        assert self.engine.connected == False

        self.engine.process_frame(
            Frame('CONNECT',
                  headers={
                      'login': MockAuthenticator.LOGIN,
                      'passcode': MockAuthenticator.PASSCODE
                  }))
        assert self.engine.connected == True

    def test_subscribe_noack(self):
        """ Test subscribing to topics and queues w/ no ACK. """
        self._connect()
        self.engine.process_frame(
            Frame('SUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn in self.qm.queues['/queue/bar']

        self.engine.process_frame(
            Frame('SUBSCRIBE', headers={'destination': '/foo/bar'}))
        assert self.conn in self.tm.topics['/foo/bar']

    def test_send(self):
        """ Test sending to a topic and queue. """
        self._connect()

        msg = Frame('SEND',
                    headers={'destination': '/queue/foo'},
                    body='QUEUEMSG-BODY')
        self.engine.process_frame(msg)
        assert msg == self.qm.messages[-1]

        msg = Frame('SEND',
                    headers={'destination': '/topic/foo'},
                    body='TOPICMSG-BODY')
        self.engine.process_frame(msg)
        assert msg == self.tm.messages[-1]

        msg = Frame('SEND', headers={}, body='TOPICMSG-BODY')
        self.engine.process_frame(msg)
        self.assertErrorFrame(self.conn.frames[-1], 'Missing destination')

    def test_receipt(self):
        """ Test pushing frames with a receipt specified. """
        self._connect()

        receipt_id = 'FOOBAR'
        msg = Frame('SEND',
                    headers={
                        'destination': '/queue/foo',
                        'receipt': receipt_id
                    },
                    body='QUEUEMSG-BODY')
        self.engine.process_frame(msg)
        rframe = self.conn.frames[-1]
        assert isinstance(rframe, ReceiptFrame)
        assert receipt_id == rframe.receipt_id

        receipt_id = 'FOOBAR2'
        self.engine.process_frame(
            Frame('SUBSCRIBE',
                  headers={
                      'destination': '/queue/bar',
                      'receipt': receipt_id
                  }))
        rframe = self.conn.frames[-1]
        assert isinstance(rframe, ReceiptFrame)
        assert receipt_id == rframe.receipt_id

    def test_subscribe_ack(self):
        """ Test subscribing to a queue with ack=true """
        self._connect()
        self.engine.process_frame(
            Frame('SUBSCRIBE',
                  headers={
                      'destination': '/queue/bar',
                      'ack': 'client'
                  }))
        assert self.conn.reliable_subscriber == True
        assert self.conn in self.qm.queues['/queue/bar']

    def test_unsubscribe(self):
        """ Test the UNSUBSCRIBE command. """
        self._connect()
        self.engine.process_frame(
            Frame('SUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn in self.qm.queues['/queue/bar']

        print self.conn.frames

        self.engine.process_frame(
            Frame('UNSUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn not in self.qm.queues['/queue/bar']

        print self.conn.frames

        self.engine.process_frame(
            Frame('UNSUBSCRIBE', headers={'destination': '/invalid'}))
        print self.conn.frames[-1]

    def test_begin(self):
        """ Test transaction BEGIN. """
        self._connect()

        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': 'abc'}))
        assert 'abc' in self.engine.transactions
        assert len(self.engine.transactions['abc']) == 0

    def test_commit(self):
        """ Test transaction COMMIT. """
        self._connect()

        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': '123'}))
        self.engine.process_frame(
            Frame('SEND',
                  headers={
                      'destination': '/dest',
                      'transaction': 'abc'
                  },
                  body='ASDF'))
        self.engine.process_frame(
            Frame('SEND',
                  headers={
                      'destination': '/dest',
                      'transaction': 'abc'
                  },
                  body='ASDF'))
        self.engine.process_frame(
            Frame('SEND',
                  headers={
                      'destination': '/dest',
                      'transaction': '123'
                  },
                  body='ASDF'))

        assert len(self.tm.messages) == 0

        self.engine.process_frame(
            Frame('COMMIT', headers={'transaction': 'abc'}))

        print self.conn.frames

        assert len(self.tm.messages) == 2

        assert len(self.engine.transactions) == 1

        self.engine.process_frame(
            Frame('COMMIT', headers={'transaction': '123'}))
        assert len(self.tm.messages) == 3

        assert len(self.engine.transactions) == 0

    def test_commit_invalid(self):
        """ Test invalid states for transaction COMMIT. """
        self._connect()

        # Send a message with invalid transaction
        f = Frame('SEND',
                  headers={
                      'destination': '/dest',
                      'transaction': '123'
                  },
                  body='ASDF')
        self.engine.process_frame(f)
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')

        # Attempt to commit invalid transaction
        self.engine.process_frame(
            Frame('COMMIT', headers={'transaction': 'abc'}))

        # Attempt to commit already-committed transaction
        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(
            Frame('SEND',
                  headers={
                      'destination': '/dest',
                      'transaction': 'abc'
                  },
                  body='FOO'))
        self.engine.process_frame(
            Frame('COMMIT', headers={'transaction': 'abc'}))

        self.engine.process_frame(
            Frame('COMMIT', headers={'transaction': 'abc'}))
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')

    def test_abort(self):
        """ Test transaction ABORT. """
        self._connect()

        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': '123'}))

        f1 = Frame('SEND',
                   headers={
                       'destination': '/dest',
                       'transaction': 'abc'
                   },
                   body='ASDF')
        self.engine.process_frame(f1)
        f2 = Frame('SEND',
                   headers={
                       'destination': '/dest',
                       'transaction': 'abc'
                   },
                   body='ASDF')
        self.engine.process_frame(f2)
        f3 = Frame('SEND',
                   headers={
                       'destination': '/dest',
                       'transaction': '123'
                   },
                   body='ASDF')
        self.engine.process_frame(f3)

        assert len(self.tm.messages) == 0

        self.engine.process_frame(
            Frame('ABORT', headers={'transaction': 'abc'}))
        assert len(self.tm.messages) == 0

        assert len(self.engine.transactions) == 1

    def test_abort_invalid(self):
        """ Test invalid states for transaction ABORT. """
        self._connect()

        self.engine.process_frame(
            Frame('ABORT', headers={'transaction': 'abc'}))

        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')

        self.engine.process_frame(
            Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(
            Frame('ABORT', headers={'transaction': 'abc'}))

        self.engine.process_frame(
            Frame('ABORT', headers={'transaction': 'abc2'}))
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')
예제 #15
0
class EngineTest(unittest.TestCase):
    
    def setUp(self):
        self.qm = MockQueueManager()
        self.tm = MockTopicManager()
        self.conn = MockConnection()
        self.auth = MockAuthenticator()
        self.engine = StompEngine(connection=self.conn,
                                  queue_manager=self.qm,
                                  topic_manager=self.tm,
                                  authenticator=None)
    def tearDown(self):
        self.conn.reset()
    
    def _connect(self):
        """ Call the engine connect() method so that we have a valid 'session'. """
        self.engine.connect(Frame('CONNECT'))
    
    def assertErrorFrame(self, frame, msgsub):
        """ Assert that the passed in frame is an error frame and that message contains specified
        string.
        """
        assert frame.command == 'ERROR'
        assert msgsub.lower() in frame.headers['message'].lower()
        
    def test_connect_no_auth(self):
        """ Test the CONNECT command with no auth required. """
        
        assert self.engine.connected == False
        self.engine.process_frame(Frame('CONNECT'))
        assert self.engine.connected == True
        
    def test_connect_auth(self):
        """ Test the CONNECT command when auth is required. """
        self.engine.authenticator = self.auth
        
        assert self.engine.connected == False
        self.engine.process_frame(Frame('CONNECT'))
        self.assertErrorFrame(self.conn.frames[-1], 'Auth')
        assert self.engine.connected == False
        
        self.engine.process_frame(Frame('CONNECT', headers={'login': MockAuthenticator.LOGIN,
                                                                'passcode': MockAuthenticator.PASSCODE}))
        assert self.engine.connected == True
    
    def test_subscribe_noack(self):
        """ Test subscribing to topics and queues w/ no ACK. """
        self._connect()
        self.engine.process_frame(Frame('SUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn in self.qm.queues['/queue/bar']
        
        self.engine.process_frame(Frame('SUBSCRIBE', headers={'destination': '/foo/bar'}))
        assert self.conn in self.tm.topics['/foo/bar']
    
    def test_send(self):
        """ Test sending to a topic and queue. """
        self._connect()
        
        msg = Frame('SEND', headers={'destination': '/queue/foo'}, body='QUEUEMSG-BODY')
        self.engine.process_frame(msg)        
        assert msg == self.qm.messages[-1]
        
        msg = Frame('SEND', headers={'destination': '/topic/foo'}, body='TOPICMSG-BODY')
        self.engine.process_frame(msg)
        assert msg == self.tm.messages[-1]
        
        msg = Frame('SEND', headers={}, body='TOPICMSG-BODY')
        self.engine.process_frame(msg)
        self.assertErrorFrame(self.conn.frames[-1], 'Missing destination')
    
    def test_receipt(self):
        """ Test pushing frames with a receipt specified. """
        self._connect()
        
        receipt_id = 'FOOBAR'
        msg = Frame('SEND', headers={'destination': '/queue/foo', 'receipt': receipt_id}, body='QUEUEMSG-BODY')
        self.engine.process_frame(msg)
        rframe = self.conn.frames[-1]
        assert isinstance(rframe, ReceiptFrame)
        assert receipt_id == rframe.receipt_id
        
        receipt_id = 'FOOBAR2'
        self.engine.process_frame(Frame('SUBSCRIBE', headers={'destination': '/queue/bar', 'receipt': receipt_id}))
        rframe = self.conn.frames[-1]
        assert isinstance(rframe, ReceiptFrame)
        assert receipt_id == rframe.receipt_id
    
    def test_subscribe_ack(self):
        """ Test subscribing to a queue with ack=true """
        self._connect()
        self.engine.process_frame(Frame('SUBSCRIBE', headers={'destination': '/queue/bar',
                                                                  'ack': 'client'}))
        assert self.conn.reliable_subscriber == True
        assert self.conn in self.qm.queues['/queue/bar']
        
    def test_unsubscribe(self):
        """ Test the UNSUBSCRIBE command. """
        self._connect()
        self.engine.process_frame(Frame('SUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn in self.qm.queues['/queue/bar']
        
        print self.conn.frames
        
        self.engine.process_frame(Frame('UNSUBSCRIBE', headers={'destination': '/queue/bar'}))
        assert self.conn not in self.qm.queues['/queue/bar']
        
        print self.conn.frames
        
        self.engine.process_frame(Frame('UNSUBSCRIBE', headers={'destination': '/invalid'}))
        print self.conn.frames[-1]
    
    def test_begin(self):
        """ Test transaction BEGIN. """
        self._connect()
        
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': 'abc'}))
        assert 'abc' in self.engine.transactions
        assert len(self.engine.transactions['abc']) == 0
    
    def test_commit(self):
        """ Test transaction COMMIT. """
        self._connect()
         
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': '123'}))
        self.engine.process_frame(Frame('SEND', headers={'destination': '/dest', 'transaction': 'abc'}, body='ASDF'))
        self.engine.process_frame(Frame('SEND', headers={'destination': '/dest', 'transaction': 'abc'}, body='ASDF'))
        self.engine.process_frame(Frame('SEND', headers={'destination': '/dest', 'transaction': '123'}, body='ASDF'))
        
        assert len(self.tm.messages) == 0
        
        self.engine.process_frame(Frame('COMMIT', headers={'transaction': 'abc'}))
        
        print self.conn.frames
        
        assert len(self.tm.messages) == 2
        
        assert len(self.engine.transactions) == 1
        
        self.engine.process_frame(Frame('COMMIT', headers={'transaction': '123'}))
        assert len(self.tm.messages) == 3
        
        assert len(self.engine.transactions) == 0
        
    def test_commit_invalid(self):
        """ Test invalid states for transaction COMMIT. """
        self._connect()
        
        # Send a message with invalid transaction 
        f = Frame('SEND', headers={'destination': '/dest', 'transaction': '123'}, body='ASDF')
        self.engine.process_frame(f)
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')
        
        # Attempt to commit invalid transaction
        self.engine.process_frame(Frame('COMMIT', headers={'transaction': 'abc'}))
        
        # Attempt to commit already-committed transaction
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(Frame('SEND', headers={'destination': '/dest', 'transaction': 'abc'}, body='FOO'))
        self.engine.process_frame(Frame('COMMIT', headers={'transaction': 'abc'}))
        
        self.engine.process_frame(Frame('COMMIT', headers={'transaction': 'abc'}))
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')
    
    def test_abort(self):
        """ Test transaction ABORT. """
        self._connect()
         
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': '123'}))
        
        f1 = Frame('SEND', headers={'destination': '/dest', 'transaction': 'abc'}, body='ASDF')
        self.engine.process_frame(f1)
        f2 = Frame('SEND', headers={'destination': '/dest', 'transaction': 'abc'}, body='ASDF')
        self.engine.process_frame(f2)
        f3 = Frame('SEND', headers={'destination': '/dest', 'transaction': '123'}, body='ASDF')
        self.engine.process_frame(f3)
        
        assert len(self.tm.messages) == 0
        
        self.engine.process_frame(Frame('ABORT', headers={'transaction': 'abc'}))
        assert len(self.tm.messages) == 0
        
        assert len(self.engine.transactions) == 1
        
    def test_abort_invalid(self):
        """ Test invalid states for transaction ABORT. """
        self._connect()
         
        self.engine.process_frame(Frame('ABORT', headers={'transaction': 'abc'}))
        
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')
        
        self.engine.process_frame(Frame('BEGIN', headers={'transaction': 'abc'}))
        self.engine.process_frame(Frame('ABORT', headers={'transaction': 'abc'}))
        
        self.engine.process_frame(Frame('ABORT', headers={'transaction': 'abc2'}))
        self.assertErrorFrame(self.conn.frames[-1], 'invalid transaction')