source: ralphm-patches/message-stanza.patch @ 73:f574beee3bca

Last change on this file since 73:f574beee3bca was 73:f574beee3bca, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Minor cleanups, improved error message handling, and upstreamed patch.

  • Upstreamed deprecation of prepareIDNName.
  • MessageProtocol? now properly handles error message stanzas.
  • Renamed methods for RosterServerProtocol?.
  • Prefer connectionInitialized for setting up stanza observers.
File size: 11.0 KB
  • wokkel/test/test_xmppim.py

    # HG changeset patch
    # Parent a94e14d79311f8efde3bd001ce4b5c8bafbb8094
    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 b  
    13361336        d = self.handleRequest(xml)
    13371337        d.addCallback(cb)
    13381338        return d
     1339
     1340
     1341
     1342class MessageTest(unittest.TestCase):
     1343    """
     1344    Tests for L{xmppim.Message}.
     1345    """
     1346
     1347    def test_defaultStanzaType(self):
     1348        """
     1349        The default stanza type is 'normal'.
     1350        """
     1351        message = xmppim.Message()
     1352        self.assertEqual('normal', message.stanzaType)
     1353
     1354
     1355    def test_fromElementStanzaTypeEmpty(self):
     1356        """
     1357        The empty stanza type is parsed as 'normal'.
     1358        """
     1359        xml = u"""<message/>"""
     1360        message = xmppim.Message.fromElement(parseXml(xml))
     1361        self.assertEqual('normal', message.stanzaType)
     1362
     1363
     1364    def test_fromElementStanzaTypeError(self):
     1365        """
     1366        The error stanza type is parsed as 'error'.
     1367        """
     1368        xml = u"""<message type='error'/>"""
     1369        message = xmppim.Message.fromElement(parseXml(xml))
     1370        self.assertEqual('error', message.stanzaType)
     1371
     1372
     1373    def test_fromElementBody(self):
     1374        """
     1375        The message body is parsed into the body attribute.
     1376        """
     1377        xml = u"""
     1378            <message>
     1379              <body>Hello</body>
     1380            </message>
     1381        """
     1382        message = xmppim.Message.fromElement(parseXml(xml))
     1383        self.assertEqual(u'Hello', message.body)
     1384
     1385
     1386    def test_fromElementSubject(self):
     1387        """
     1388        The message subject is parsed into the subject attribute.
     1389        """
     1390        xml = u"""
     1391            <message>
     1392              <subject>Greeting</subject>
     1393            </message>
     1394        """
     1395        message = xmppim.Message.fromElement(parseXml(xml))
     1396        self.assertEqual(u'Greeting', message.subject)
     1397
     1398
     1399    def test_toElement(self):
     1400        message = xmppim.Message(body=u'Hello', subject=u'Greeting')
     1401        message.stanzaType = u'chat'
     1402
     1403        element = message.toElement()
     1404
     1405        self.assertEqual(u'chat', element.getAttribute(u'type'))
     1406        self.assertEqual(u'Hello', unicode(element.body))
     1407        self.assertEqual(u'Greeting', unicode(element.subject))
     1408
     1409
     1410
     1411class MessageProtocolTest(unittest.TestCase):
     1412    """
     1413    Tests for L{xmppim.MessageProtocol}.
     1414    """
     1415
     1416    def setUp(self):
     1417        self.stub = XmlStreamStub()
     1418        self.protocol = xmppim.MessageProtocol()
     1419        self.protocol.makeConnection(self.stub.xmlstream)
     1420        self.protocol.connectionInitialized()
     1421
     1422
     1423    def test_messageReceived(self):
     1424        """
     1425        Message stanzas are observed.
     1426        """
     1427        received = []
     1428        self.patch(self.protocol, 'messageReceived', received.append)
     1429
     1430        xml = """
     1431          <message to='example.org'>
     1432            <body>Hello</body>
     1433          </message>
     1434        """
     1435
     1436        message = parseXml(xml)
     1437        self.stub.send(message)
     1438
     1439        self.assertEqual(1, len(received))
     1440        stanza = received[-1]
     1441        self.assertEqual(u'Hello', stanza.body)
     1442
     1443
     1444    def test_errorReceived(self):
     1445        """
     1446        Message stanzas of type error are received by errorReceived.
     1447        """
     1448        messagesReceived = []
     1449        errorsReceived = []
     1450        self.patch(self.protocol, 'messageReceived', messagesReceived.append)
     1451        self.patch(self.protocol, 'errorReceived', errorsReceived.append)
     1452
     1453        xml = """
     1454          <message to='example.org' type='error'>
     1455            <error type='cancel'>
     1456              <service-unavailable
     1457                  xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     1458            </error>
     1459          </message>
     1460        """
     1461
     1462        message = parseXml(xml)
     1463        self.stub.send(message)
     1464
     1465        self.assertEqual(0, len(messagesReceived))
     1466        self.assertEqual(1, len(errorsReceived))
     1467        stanza = errorsReceived[-1]
     1468        self.assertEqual(u'service-unavailable', stanza.exception.condition)
     1469
     1470
     1471    def test_messageHandled(self):
     1472        """
     1473        Message stanzas marked as handled are ignored.
     1474        """
     1475        received = []
     1476        self.patch(self.protocol, 'messageReceived', received.append)
     1477
     1478        xml = """
     1479          <message to='example.org'>
     1480            <body>Hello</body>
     1481          </message>
     1482        """
     1483
     1484        message = parseXml(xml)
     1485        message.handled = True
     1486        self.stub.send(message)
     1487
     1488        self.assertEqual(0, len(received))
     1489
     1490
     1491    def test_onMessage(self):
     1492        """
     1493        The deprecated onMessage is called if present.
     1494        """
     1495        received = []
     1496        self.protocol.onMessage = received.append
     1497
     1498        xml = """
     1499          <message to='example.org'>
     1500            <body>Hello</body>
     1501          </message>
     1502        """
     1503
     1504        message = parseXml(xml)
     1505        self.stub.send(message)
     1506
     1507        self.assertEqual(1, len(received))
     1508        element = received[-1]
     1509        self.assertEqual(u'message', element.name)
     1510        self.assertEqual(u'normal', element['type'])
     1511
     1512        warnings = self.flushWarnings()
     1513        self.assertEqual(1, len(warnings))
     1514
     1515        self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
     1516                         "was deprecated in Wokkel 0.8.0; "
     1517                         "please use MessageProtocol.messageReceived instead.",
     1518                         warnings[0]['message'])
     1519
     1520
     1521    def test_onMessageError(self):
     1522        """
     1523        The deprecated onMessage is not called for error messages.
     1524        """
     1525        received = []
     1526        self.protocol.onMessage = received.append
     1527
     1528        xml = """
     1529          <message to='example.org' type='error'>
     1530            <error type='cancel'>
     1531              <service-unavailable
     1532                  xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     1533            </error>
     1534          </message>
     1535        """
     1536
     1537        message = parseXml(xml)
     1538        self.stub.send(message)
     1539
     1540        self.assertEqual(0, len(received))
     1541
     1542        warnings = self.flushWarnings()
     1543        self.assertEqual(1, len(warnings))
     1544
     1545        self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
     1546                         "was deprecated in Wokkel 0.8.0; "
     1547                         "please use MessageProtocol.messageReceived instead.",
     1548                         warnings[0]['message'])
  • wokkel/xmppim.py

    diff --git a/wokkel/xmppim.py b/wokkel/xmppim.py
    a b  
    2020from wokkel.generic import ErrorStanza, Stanza, Request
    2121from wokkel.subprotocols import IQHandlerMixin
    2222from wokkel.subprotocols import XMPPHandler
     23from wokkel.subprotocols import asyncObserver
    2324
    2425NS_XML = 'http://www.w3.org/XML/1998/namespace'
    2526NS_ROSTER = 'jabber:iq:roster'
     
    10061007    A message stanza.
    10071008    """
    10081009
    1009     stanzaKind = 'message'
     1010    stanzaKind = u'message'
     1011    stanzaType = u'normal'
     1012    _messageTypes = u'normal', u'chat', u'headline', u'groupchat', u'error'
    10101013
    10111014    childParsers = {
    1012             (None, 'body'): '_childParser_body',
    1013             (None, 'subject'): '_childParser_subject',
     1015            (None, u'body'): '_childParser_body',
     1016            (None, u'subject'): '_childParser_subject',
    10141017            }
    10151018
    10161019    def __init__(self, recipient=None, sender=None, body=None, subject=None):
     
    10311034        element = Stanza.toElement(self)
    10321035
    10331036        if self.body:
    1034             element.addElement('body', content=self.body)
     1037            element.addElement(u'body', content=self.body)
    10351038        if self.subject:
    1036             element.addElement('subject', content=self.subject)
     1039            element.addElement(u'subject', content=self.subject)
    10371040
    10381041        return element
    10391042
    10401043
     1044    @classmethod
     1045    def fromElement(cls, element):
     1046        stanza = super(Message, cls).fromElement(element)
     1047        if stanza.stanzaType not in cls._messageTypes:
     1048            stanza.stanzaType = u'normal'
     1049        return stanza
     1050
     1051
    10411052
    10421053class MessageProtocol(XMPPHandler):
    10431054    """
    10441055    Generic XMPP subprotocol handler for incoming message stanzas.
     1056
     1057    @cvar messageFactory: The class to parse messages into. Its C{fromElement}
     1058        will be called with the received L{domish.Element}. Defaults to
     1059        L{Message}.
    10451060    """
    10461061
    1047     messageTypes = None, 'normal', 'chat', 'headline', 'groupchat'
     1062    messageFactory = Message
    10481063
    10491064    def connectionInitialized(self):
    10501065        self.xmlstream.addObserver("/message", self._onMessage)
    10511066
    1052     def _onMessage(self, message):
    1053         if message.handled:
    1054             return
    10551067
    1056         messageType = message.getAttribute("type")
     1068    @asyncObserver
     1069    def _onMessage(self, element):
     1070        if element.getAttribute(u'type') == u'error':
     1071            stanza = ErrorStanza.fromElement(element)
     1072            return self.errorReceived(stanza)
     1073        else:
     1074            stanza = self.messageFactory.fromElement(element)
     1075            return self.messageReceived(stanza)
    10571076
    1058         if messageType == 'error':
    1059             return
    10601077
    1061         if messageType not in self.messageTypes:
    1062             message["type"] = 'normal'
     1078    def errorReceived(self, stanza):
     1079        """
     1080        Called when a message stanza of type error was received.
    10631081
    1064         self.onMessage(message)
     1082        @type stanza: L{ErrorStanza}
     1083        """
     1084        if hasattr(self, 'onMessage'):
     1085            warnings.warn(
     1086                "wokkel.xmppim.MessageProtocol.onMessage "
     1087                "was deprecated in Wokkel 0.8.0; "
     1088                "please use MessageProtocol.messageReceived instead.",
     1089                DeprecationWarning)
     1090            return False
    10651091
    1066     def onMessage(self, message):
     1092
     1093    def messageReceived(self, message):
    10671094        """
    10681095        Called when a message stanza was received.
     1096
     1097        Override this method to handle a message. To let other handlers
     1098        process this message, return C{False} (synchronously).
     1099
     1100        The caller of this method uses L{asyncObserver}, so that
     1101        deferreds can also be returned and exceptions are handled properly.
     1102
     1103        @rtype: L{bool} or L{Deferred}
    10691104        """
     1105        if hasattr(self, 'onMessage'):
     1106            warnings.warn(
     1107                "wokkel.xmppim.MessageProtocol.onMessage "
     1108                "was deprecated in Wokkel 0.8.0; "
     1109                "please use MessageProtocol.messageReceived instead.",
     1110                DeprecationWarning)
     1111
     1112            if message.stanzaType == u'normal':
     1113                # Override stanza type for backwards compatibility.
     1114                message.element[u'type'] = u'normal'
     1115
     1116            self.onMessage(message.element)
     1117            return True
Note: See TracBrowser for help on using the repository browser.