# 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)