source: ralphm-patches/message-stanza.patch @ 83:255aae0cf8c5

Last change on this file since 83:255aae0cf8c5 was 75:9b7b8b99da61, checked in by Ralph Meijer <ralphm@…>, 6 years ago

Honor 'handled' on message stanza for backwards compat.

File size: 11.0 KB
RevLine 
[72]1# HG changeset patch
[75]2# Parent 5df04f703f9024cd3a73d51117df93e7f56fdd7a
[73]3Parse message stanzas into Message objects.
4
5This replaces `MessageProtocol.onMessage` with `messageReceived` and
6`errorReceived`.
7
8Incoming messages stanzas are parsed by the `fromElement` class method
9of the class set in `MessageProtocol.messageFactory`. This defaults to
10the `Message` class.  The resulting object is passed to
11`messageReceived`.
12
13If the message type was `error`, the message is parsed as a
14`wokkel.generic.ErrorStanza` instead, and passed to `errorReceived`.
15
16`Message` now normalizes the message type of empty or unknown values to
17`normal` per RFC 6120.
[72]18
19diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py
20--- a/wokkel/test/test_xmppim.py
21+++ b/wokkel/test/test_xmppim.py
[73]22@@ -1336,3 +1336,213 @@
[72]23         d = self.handleRequest(xml)
24         d.addCallback(cb)
25         return d
26+
27+
28+
29+class MessageTest(unittest.TestCase):
30+    """
31+    Tests for L{xmppim.Message}.
32+    """
33+
34+    def test_defaultStanzaType(self):
35+        """
36+        The default stanza type is 'normal'.
37+        """
38+        message = xmppim.Message()
39+        self.assertEqual('normal', message.stanzaType)
40+
41+
42+    def test_fromElementStanzaTypeEmpty(self):
43+        """
44+        The empty stanza type is parsed as 'normal'.
45+        """
46+        xml = u"""<message/>"""
47+        message = xmppim.Message.fromElement(parseXml(xml))
48+        self.assertEqual('normal', message.stanzaType)
49+
50+
51+    def test_fromElementStanzaTypeError(self):
52+        """
53+        The error stanza type is parsed as 'error'.
54+        """
55+        xml = u"""<message type='error'/>"""
56+        message = xmppim.Message.fromElement(parseXml(xml))
57+        self.assertEqual('error', message.stanzaType)
58+
59+
60+    def test_fromElementBody(self):
61+        """
62+        The message body is parsed into the body attribute.
63+        """
64+        xml = u"""
65+            <message>
66+              <body>Hello</body>
67+            </message>
68+        """
69+        message = xmppim.Message.fromElement(parseXml(xml))
70+        self.assertEqual(u'Hello', message.body)
71+
72+
73+    def test_fromElementSubject(self):
74+        """
75+        The message subject is parsed into the subject attribute.
76+        """
77+        xml = u"""
78+            <message>
79+              <subject>Greeting</subject>
80+            </message>
81+        """
82+        message = xmppim.Message.fromElement(parseXml(xml))
83+        self.assertEqual(u'Greeting', message.subject)
84+
85+
86+    def test_toElement(self):
87+        message = xmppim.Message(body=u'Hello', subject=u'Greeting')
88+        message.stanzaType = u'chat'
89+
90+        element = message.toElement()
91+
92+        self.assertEqual(u'chat', element.getAttribute(u'type'))
93+        self.assertEqual(u'Hello', unicode(element.body))
94+        self.assertEqual(u'Greeting', unicode(element.subject))
95+
96+
97+
98+class MessageProtocolTest(unittest.TestCase):
99+    """
100+    Tests for L{xmppim.MessageProtocol}.
101+    """
102+
103+    def setUp(self):
104+        self.stub = XmlStreamStub()
105+        self.protocol = xmppim.MessageProtocol()
106+        self.protocol.makeConnection(self.stub.xmlstream)
107+        self.protocol.connectionInitialized()
108+
109+
110+    def test_messageReceived(self):
111+        """
112+        Message stanzas are observed.
113+        """
114+        received = []
115+        self.patch(self.protocol, 'messageReceived', received.append)
116+
117+        xml = """
118+          <message to='example.org'>
119+            <body>Hello</body>
120+          </message>
121+        """
122+
123+        message = parseXml(xml)
124+        self.stub.send(message)
125+
126+        self.assertEqual(1, len(received))
127+        stanza = received[-1]
128+        self.assertEqual(u'Hello', stanza.body)
129+
130+
[73]131+    def test_errorReceived(self):
132+        """
133+        Message stanzas of type error are received by errorReceived.
134+        """
135+        messagesReceived = []
136+        errorsReceived = []
137+        self.patch(self.protocol, 'messageReceived', messagesReceived.append)
138+        self.patch(self.protocol, 'errorReceived', errorsReceived.append)
139+
140+        xml = """
141+          <message to='example.org' type='error'>
142+            <error type='cancel'>
143+              <service-unavailable
144+                  xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
145+            </error>
146+          </message>
147+        """
148+
149+        message = parseXml(xml)
150+        self.stub.send(message)
151+
152+        self.assertEqual(0, len(messagesReceived))
153+        self.assertEqual(1, len(errorsReceived))
154+        stanza = errorsReceived[-1]
155+        self.assertEqual(u'service-unavailable', stanza.exception.condition)
156+
157+
[72]158+    def test_messageHandled(self):
159+        """
160+        Message stanzas marked as handled are ignored.
161+        """
162+        received = []
163+        self.patch(self.protocol, 'messageReceived', received.append)
164+
165+        xml = """
166+          <message to='example.org'>
167+            <body>Hello</body>
168+          </message>
169+        """
170+
171+        message = parseXml(xml)
172+        message.handled = True
173+        self.stub.send(message)
174+
175+        self.assertEqual(0, len(received))
176+
177+
178+    def test_onMessage(self):
179+        """
180+        The deprecated onMessage is called if present.
181+        """
182+        received = []
183+        self.protocol.onMessage = received.append
184+
185+        xml = """
186+          <message to='example.org'>
187+            <body>Hello</body>
188+          </message>
189+        """
190+
191+        message = parseXml(xml)
192+        self.stub.send(message)
193+
194+        self.assertEqual(1, len(received))
195+        element = received[-1]
196+        self.assertEqual(u'message', element.name)
197+        self.assertEqual(u'normal', element['type'])
198+
199+        warnings = self.flushWarnings()
200+        self.assertEqual(1, len(warnings))
201+
202+        self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
203+                         "was deprecated in Wokkel 0.8.0; "
204+                         "please use MessageProtocol.messageReceived instead.",
205+                         warnings[0]['message'])
206+
207+
208+    def test_onMessageError(self):
209+        """
210+        The deprecated onMessage is not called for error messages.
211+        """
212+        received = []
213+        self.protocol.onMessage = received.append
214+
215+        xml = """
216+          <message to='example.org' type='error'>
217+            <error type='cancel'>
218+              <service-unavailable
219+                  xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
220+            </error>
221+          </message>
222+        """
223+
224+        message = parseXml(xml)
225+        self.stub.send(message)
226+
227+        self.assertEqual(0, len(received))
228+
229+        warnings = self.flushWarnings()
230+        self.assertEqual(1, len(warnings))
231+
232+        self.assertEqual("wokkel.xmppim.MessageProtocol.onMessage "
233+                         "was deprecated in Wokkel 0.8.0; "
234+                         "please use MessageProtocol.messageReceived instead.",
235+                         warnings[0]['message'])
236diff --git a/wokkel/xmppim.py b/wokkel/xmppim.py
237--- a/wokkel/xmppim.py
238+++ b/wokkel/xmppim.py
239@@ -20,6 +20,7 @@
240 from wokkel.generic import ErrorStanza, Stanza, Request
241 from wokkel.subprotocols import IQHandlerMixin
242 from wokkel.subprotocols import XMPPHandler
243+from wokkel.subprotocols import asyncObserver
244 
245 NS_XML = 'http://www.w3.org/XML/1998/namespace'
246 NS_ROSTER = 'jabber:iq:roster'
247@@ -1006,11 +1007,13 @@
248     A message stanza.
249     """
250 
251-    stanzaKind = 'message'
252+    stanzaKind = u'message'
253+    stanzaType = u'normal'
254+    _messageTypes = u'normal', u'chat', u'headline', u'groupchat', u'error'
255 
256     childParsers = {
257-            (None, 'body'): '_childParser_body',
258-            (None, 'subject'): '_childParser_subject',
259+            (None, u'body'): '_childParser_body',
260+            (None, u'subject'): '_childParser_subject',
261             }
262 
263     def __init__(self, recipient=None, sender=None, body=None, subject=None):
[73]264@@ -1031,39 +1034,84 @@
[72]265         element = Stanza.toElement(self)
266 
267         if self.body:
268-            element.addElement('body', content=self.body)
269+            element.addElement(u'body', content=self.body)
270         if self.subject:
271-            element.addElement('subject', content=self.subject)
272+            element.addElement(u'subject', content=self.subject)
273 
274         return element
275 
276 
277+    @classmethod
278+    def fromElement(cls, element):
279+        stanza = super(Message, cls).fromElement(element)
280+        if stanza.stanzaType not in cls._messageTypes:
281+            stanza.stanzaType = u'normal'
282+        return stanza
283+
284+
285 
286 class MessageProtocol(XMPPHandler):
287     """
288     Generic XMPP subprotocol handler for incoming message stanzas.
289+
[73]290+    @cvar messageFactory: The class to parse messages into. Its C{fromElement}
[72]291+        will be called with the received L{domish.Element}. Defaults to
292+        L{Message}.
293     """
294 
295-    messageTypes = None, 'normal', 'chat', 'headline', 'groupchat'
[73]296+    messageFactory = Message
[72]297 
298     def connectionInitialized(self):
299         self.xmlstream.addObserver("/message", self._onMessage)
300 
301-    def _onMessage(self, message):
302-        if message.handled:
303-            return
304 
305-        messageType = message.getAttribute("type")
306+    @asyncObserver
307+    def _onMessage(self, element):
[73]308+        if element.getAttribute(u'type') == u'error':
309+            stanza = ErrorStanza.fromElement(element)
310+            return self.errorReceived(stanza)
311+        else:
312+            stanza = self.messageFactory.fromElement(element)
313+            return self.messageReceived(stanza)
[72]314 
315-        if messageType == 'error':
316-            return
317 
318-        if messageType not in self.messageTypes:
319-            message["type"] = 'normal'
[73]320+    def errorReceived(self, stanza):
321+        """
322+        Called when a message stanza of type error was received.
323 
[72]324-        self.onMessage(message)
[73]325+        @type stanza: L{ErrorStanza}
326+        """
327+        if hasattr(self, 'onMessage'):
328+            warnings.warn(
329+                "wokkel.xmppim.MessageProtocol.onMessage "
330+                "was deprecated in Wokkel 0.8.0; "
331+                "please use MessageProtocol.messageReceived instead.",
332+                DeprecationWarning)
333+            return False
334 
[72]335-    def onMessage(self, message):
[73]336+
[72]337+    def messageReceived(self, message):
338         """
339         Called when a message stanza was received.
340+
341+        Override this method to handle a message. To let other handlers
342+        process this message, return C{False} (synchronously).
343+
344+        The caller of this method uses L{asyncObserver}, so that
345+        deferreds can also be returned and exceptions are handled properly.
346+
347+        @rtype: L{bool} or L{Deferred}
348         """
349+        if hasattr(self, 'onMessage'):
350+            warnings.warn(
351+                "wokkel.xmppim.MessageProtocol.onMessage "
352+                "was deprecated in Wokkel 0.8.0; "
353+                "please use MessageProtocol.messageReceived instead.",
354+                DeprecationWarning)
355+
356+            if message.stanzaType == u'normal':
357+                # Override stanza type for backwards compatibility.
358+                message.element[u'type'] = u'normal'
359+
360+            self.onMessage(message.element)
[75]361+            return getattr(message, "handled", False)
Note: See TracBrowser for help on using the repository browser.