# HG changeset patch
# Parent 5df04f703f9024cd3a73d51117df93e7f56fdd7a
Parse message stanzas into Message objects.
This replaces `MessageProtocol.onMessage` with `messageReceived` and
`errorReceived`.
Incoming messages stanzas are parsed by the `fromElement` class method
of the class set in `MessageProtocol.messageFactory`. This defaults to
the `Message` class. The resulting object is passed to
`messageReceived`.
If the message type was `error`, the message is parsed as a
`wokkel.generic.ErrorStanza` instead, and passed to `errorReceived`.
`Message` now normalizes the message type of empty or unknown values to
`normal` per RFC 6120.
diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py
--- a/wokkel/test/test_xmppim.py
+++ b/wokkel/test/test_xmppim.py
@@ -1336,3 +1336,213 @@
d = self.handleRequest(xml)
d.addCallback(cb)
return d
+
+
+
+class MessageTest(unittest.TestCase):
+ """
+ Tests for L{xmppim.Message}.
+ """
+
+ def test_defaultStanzaType(self):
+ """
+ The default stanza type is 'normal'.
+ """
+ message = xmppim.Message()
+ self.assertEqual('normal', message.stanzaType)
+
+
+ def test_fromElementStanzaTypeEmpty(self):
+ """
+ The empty stanza type is parsed as 'normal'.
+ """
+ xml = u""""""
+ message = xmppim.Message.fromElement(parseXml(xml))
+ self.assertEqual('normal', message.stanzaType)
+
+
+ def test_fromElementStanzaTypeError(self):
+ """
+ The error stanza type is parsed as 'error'.
+ """
+ xml = u""""""
+ message = xmppim.Message.fromElement(parseXml(xml))
+ self.assertEqual('error', message.stanzaType)
+
+
+ def test_fromElementBody(self):
+ """
+ The message body is parsed into the body attribute.
+ """
+ xml = u"""
+
+ Hello
+
+ """
+ message = xmppim.Message.fromElement(parseXml(xml))
+ self.assertEqual(u'Hello', message.body)
+
+
+ def test_fromElementSubject(self):
+ """
+ The message subject is parsed into the subject attribute.
+ """
+ xml = u"""
+
+ Greeting
+
+ """
+ message = xmppim.Message.fromElement(parseXml(xml))
+ self.assertEqual(u'Greeting', message.subject)
+
+
+ def test_toElement(self):
+ message = xmppim.Message(body=u'Hello', subject=u'Greeting')
+ message.stanzaType = u'chat'
+
+ element = message.toElement()
+
+ self.assertEqual(u'chat', element.getAttribute(u'type'))
+ self.assertEqual(u'Hello', unicode(element.body))
+ self.assertEqual(u'Greeting', unicode(element.subject))
+
+
+
+class MessageProtocolTest(unittest.TestCase):
+ """
+ Tests for L{xmppim.MessageProtocol}.
+ """
+
+ def setUp(self):
+ self.stub = XmlStreamStub()
+ self.protocol = xmppim.MessageProtocol()
+ self.protocol.makeConnection(self.stub.xmlstream)
+ self.protocol.connectionInitialized()
+
+
+ def test_messageReceived(self):
+ """
+ Message stanzas are observed.
+ """
+ received = []
+ self.patch(self.protocol, 'messageReceived', received.append)
+
+ xml = """
+
+ Hello
+
+ """
+
+ message = parseXml(xml)
+ self.stub.send(message)
+
+ self.assertEqual(1, len(received))
+ stanza = received[-1]
+ self.assertEqual(u'Hello', stanza.body)
+
+
+ def test_errorReceived(self):
+ """
+ Message stanzas of type error are received by errorReceived.
+ """
+ messagesReceived = []
+ errorsReceived = []
+ self.patch(self.protocol, 'messageReceived', messagesReceived.append)
+ self.patch(self.protocol, 'errorReceived', errorsReceived.append)
+
+ xml = """
+
+
+
+
+
+ """
+
+ message = parseXml(xml)
+ self.stub.send(message)
+
+ self.assertEqual(0, len(messagesReceived))
+ self.assertEqual(1, len(errorsReceived))
+ stanza = errorsReceived[-1]
+ self.assertEqual(u'service-unavailable', stanza.exception.condition)
+
+
+ def test_messageHandled(self):
+ """
+ Message stanzas marked as handled are ignored.
+ """
+ received = []
+ self.patch(self.protocol, 'messageReceived', received.append)
+
+ xml = """
+
+ Hello
+
+ """
+
+ message = parseXml(xml)
+ message.handled = True
+ self.stub.send(message)
+
+ self.assertEqual(0, len(received))
+
+
+ def test_onMessage(self):
+ """
+ The deprecated onMessage is called if present.
+ """
+ received = []
+ self.protocol.onMessage = received.append
+
+ xml = """
+
+ Hello
+
+ """
+
+ message = parseXml(xml)
+ self.stub.send(message)
+
+ self.assertEqual(1, len(received))
+ element = received[-1]
+ self.assertEqual(u'message', element.name)
+ self.assertEqual(u'normal', element['type'])
+
+ warnings = self.flushWarnings()
+ self.assertEqual(1, len(warnings))
+
+ self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
+ "was deprecated in Wokkel 0.8.0; "
+ "please use MessageProtocol.messageReceived instead.",
+ warnings[0]['message'])
+
+
+ def test_onMessageError(self):
+ """
+ The deprecated onMessage is not called for error messages.
+ """
+ received = []
+ self.protocol.onMessage = received.append
+
+ xml = """
+
+
+
+
+
+ """
+
+ message = parseXml(xml)
+ self.stub.send(message)
+
+ self.assertEqual(0, len(received))
+
+ warnings = self.flushWarnings()
+ self.assertEqual(1, len(warnings))
+
+ self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
+ "was deprecated in Wokkel 0.8.0; "
+ "please use MessageProtocol.messageReceived instead.",
+ warnings[0]['message'])
diff --git a/wokkel/xmppim.py b/wokkel/xmppim.py
--- a/wokkel/xmppim.py
+++ b/wokkel/xmppim.py
@@ -20,6 +20,7 @@
from wokkel.generic import ErrorStanza, Stanza, Request
from wokkel.subprotocols import IQHandlerMixin
from wokkel.subprotocols import XMPPHandler
+from wokkel.subprotocols import asyncObserver
NS_XML = 'http://www.w3.org/XML/1998/namespace'
NS_ROSTER = 'jabber:iq:roster'
@@ -1006,11 +1007,13 @@
A message stanza.
"""
- stanzaKind = 'message'
+ stanzaKind = u'message'
+ stanzaType = u'normal'
+ _messageTypes = u'normal', u'chat', u'headline', u'groupchat', u'error'
childParsers = {
- (None, 'body'): '_childParser_body',
- (None, 'subject'): '_childParser_subject',
+ (None, u'body'): '_childParser_body',
+ (None, u'subject'): '_childParser_subject',
}
def __init__(self, recipient=None, sender=None, body=None, subject=None):
@@ -1031,39 +1034,84 @@
element = Stanza.toElement(self)
if self.body:
- element.addElement('body', content=self.body)
+ element.addElement(u'body', content=self.body)
if self.subject:
- element.addElement('subject', content=self.subject)
+ element.addElement(u'subject', content=self.subject)
return element
+ @classmethod
+ def fromElement(cls, element):
+ stanza = super(Message, cls).fromElement(element)
+ if stanza.stanzaType not in cls._messageTypes:
+ stanza.stanzaType = u'normal'
+ return stanza
+
+
class MessageProtocol(XMPPHandler):
"""
Generic XMPP subprotocol handler for incoming message stanzas.
+
+ @cvar messageFactory: The class to parse messages into. Its C{fromElement}
+ will be called with the received L{domish.Element}. Defaults to
+ L{Message}.
"""
- messageTypes = None, 'normal', 'chat', 'headline', 'groupchat'
+ messageFactory = Message
def connectionInitialized(self):
self.xmlstream.addObserver("/message", self._onMessage)
- def _onMessage(self, message):
- if message.handled:
- return
- messageType = message.getAttribute("type")
+ @asyncObserver
+ def _onMessage(self, element):
+ if element.getAttribute(u'type') == u'error':
+ stanza = ErrorStanza.fromElement(element)
+ return self.errorReceived(stanza)
+ else:
+ stanza = self.messageFactory.fromElement(element)
+ return self.messageReceived(stanza)
- if messageType == 'error':
- return
- if messageType not in self.messageTypes:
- message["type"] = 'normal'
+ def errorReceived(self, stanza):
+ """
+ Called when a message stanza of type error was received.
- self.onMessage(message)
+ @type stanza: L{ErrorStanza}
+ """
+ if hasattr(self, 'onMessage'):
+ warnings.warn(
+ "wokkel.xmppim.MessageProtocol.onMessage "
+ "was deprecated in Wokkel 0.8.0; "
+ "please use MessageProtocol.messageReceived instead.",
+ DeprecationWarning)
+ return False
- def onMessage(self, message):
+
+ def messageReceived(self, message):
"""
Called when a message stanza was received.
+
+ Override this method to handle a message. To let other handlers
+ process this message, return C{False} (synchronously).
+
+ The caller of this method uses L{asyncObserver}, so that
+ deferreds can also be returned and exceptions are handled properly.
+
+ @rtype: L{bool} or L{Deferred}
"""
+ if hasattr(self, 'onMessage'):
+ warnings.warn(
+ "wokkel.xmppim.MessageProtocol.onMessage "
+ "was deprecated in Wokkel 0.8.0; "
+ "please use MessageProtocol.messageReceived instead.",
+ DeprecationWarning)
+
+ if message.stanzaType == u'normal':
+ # Override stanza type for backwards compatibility.
+ message.element[u'type'] = u'normal'
+
+ self.onMessage(message.element)
+ return getattr(message, "handled", False)