Changeset 49:537d1413b661 in ralphm-patches for roster_item.patch


Ignore:
Timestamp:
May 25, 2011, 9:50:21 AM (11 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Message:

Save work after moving stuff to keep from wokkel.xmppim to wokkel.im.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • roster_item.patch

    r36 r49  
    1 diff -r c9d1bf0b783d wokkel/test/test_xmppim.py
    2 --- a/wokkel/test/test_xmppim.py        Sun Jan 10 15:01:41 2010 +0100
    3 +++ b/wokkel/test/test_xmppim.py        Fri Feb 12 19:49:36 2010 +0100
    4 @@ -449,6 +449,96 @@
    5  
    6  
    7  
    8 +class RosterItemTest(unittest.TestCase):
    9 +    """
    10 +    Tests for L{xmppim.RosterItem}.
    11 +    """
    12 +
    13 +    def test_toElement(self):
    14 +        item = xmppim.RosterItem(JID('user@example.org'))
    15 +        element = item.toElement()
    16 +        self.assertEquals('item', element.name)
    17 +        self.assertEquals(NS_ROSTER, element.uri)
    18 +        self.assertTrue(element.hasAttribute('subscription'))
    19 +        self.assertFalse(element.hasAttribute('ask'))
    20 +        self.assertFalse(element.hasAttribute('name'))
    21 +        self.assertEquals(0, len(list(element.elements())))
    22 +
    23 +
    24 +    def test_toElementJID(self):
    25 +        item = xmppim.RosterItem(JID('user@example.org'))
    26 +        element = item.toElement()
    27 +        self.assertEquals(u'user@example.org', element.getAttribute('jid'))
    28 +
    29 +
    30 +    def test_toElementSubscriptionNone(self):
    31 +        item = xmppim.RosterItem(JID('user@example.org'),
    32 +                                 subscriptionTo=False,
    33 +                                 subscriptionFrom=False)
    34 +        element = item.toElement()
    35 +        self.assertEquals('none', element.getAttribute('subscription'))
    36 +
    37 +
    38 +    def test_toElementSubscriptionTo(self):
    39 +        item = xmppim.RosterItem(JID('user@example.org'),
    40 +                                 subscriptionTo=True,
    41 +                                 subscriptionFrom=False)
    42 +        element = item.toElement()
    43 +        self.assertEquals('to', element.getAttribute('subscription'))
    44 +
    45 +
    46 +    def test_toElementSubscriptionFrom(self):
    47 +        item = xmppim.RosterItem(JID('user@example.org'),
    48 +                                 subscriptionTo=False,
    49 +                                 subscriptionFrom=True)
    50 +        element = item.toElement()
    51 +        self.assertEquals('from', element.getAttribute('subscription'))
    52 +
    53 +
    54 +    def test_toElementSubscriptionBoth(self):
    55 +        item = xmppim.RosterItem(JID('user@example.org'),
    56 +                                 subscriptionTo=True,
    57 +                                 subscriptionFrom=True)
    58 +        element = item.toElement()
    59 +        self.assertEquals('both', element.getAttribute('subscription'))
    60 +
    61 +
    62 +    def test_toElementRemove(self):
    63 +        item = xmppim.RosterItem(JID('user@example.org'),
    64 +                                 remove=True)
    65 +        element = item.toElement()
    66 +        self.assertEquals('remove', element.getAttribute('subscription'))
    67 +
    68 +
    69 +    def test_toElementAsk(self):
    70 +        item = xmppim.RosterItem(JID('user@example.org'),
    71 +                                 ask=True)
    72 +        element = item.toElement()
    73 +        self.assertEquals('subscribe', element.getAttribute('ask'))
    74 +
    75 +
    76 +    def test_toElementName(self):
    77 +        item = xmppim.RosterItem(JID('user@example.org'),
    78 +                                 name='Joe User')
    79 +        element = item.toElement()
    80 +        self.assertEquals(u'Joe User', element.getAttribute('name'))
    81 +
    82 +
    83 +    def test_toElementGroups(self):
    84 +        groups = set(['Friends', 'Jabber'])
    85 +        item = xmppim.RosterItem(JID('user@example.org'),
    86 +                                 groups=groups)
    87 +
    88 +        element = item.toElement()
    89 +        foundGroups = set()
    90 +        for child in element.elements():
    91 +            if child.uri == NS_ROSTER and child.name == 'group':
    92 +                foundGroups.add(unicode(child))
    93 +
    94 +        self.assertEqual(groups, foundGroups)
    95 +
    96 +
    97 +
    98  class RosterClientProtocolTest(unittest.TestCase):
     1diff -r c91f18811c37 wokkel/im.py
     2--- a/wokkel/im.py      Mon May 23 18:19:44 2011 +0200
     3+++ b/wokkel/im.py      Wed May 25 09:24:29 2011 +0200
     4@@ -7,21 +7,27 @@
     5 XMPP IM protocol support.
     6 
     7 This module provides generic implementations for the protocols defined in
     8-U{RFC 3921<http://www.xmpp.org/rfcs/rfc3921.html>} (XMPP IM).
     9-
     10-All of it should eventually move to Twisted.
     11+U{RFC 6121<http://www.xmpp.org/rfcs/rfc6121.html>} (XMPP IM).
     12 """
     13 
     14+import warnings
     15+
     16+from twisted.internet import defer
     17 from twisted.words.protocols.jabber.jid import JID, internJID
     18+from twisted.words.protocols.jabber import error
     19 from twisted.words.xish import domish
     20 
     21 from wokkel.compat import IQ
     22 from wokkel.generic import ErrorStanza, Stanza
     23-from wokkel.subprotocols import XMPPHandler
     24+from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
     25 
     26 NS_XML = 'http://www.w3.org/XML/1998/namespace'
     27 NS_ROSTER = 'jabber:iq:roster'
     28 
     29+XPATH_ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER
     30+
     31+
     32+
     33 class BasePresence(Stanza):
    9934     """
    100      Tests for L{xmppim.RosterClientProtocol}.
    101 diff -r c9d1bf0b783d wokkel/xmppim.py
    102 --- a/wokkel/xmppim.py  Sun Jan 10 15:01:41 2010 +0100
    103 +++ b/wokkel/xmppim.py  Fri Feb 12 19:49:36 2010 +0100
    104 @@ -588,7 +588,7 @@
     35     Stanza of kind presence.
     36@@ -349,8 +355,8 @@
     37 
     38     This represents one contact from an XMPP contact list known as roster.
     39 
     40-    @ivar jid: The JID of the contact.
     41-    @type jid: L{JID}
     42+    @ivar entity: The JID of the contact.
     43+    @type entity: L{JID}
     44     @ivar name: The optional associated nickname for this contact.
     45     @type name: C{unicode}
     46     @ivar subscriptionTo: Subscription state to contact's presence. If C{True},
     47@@ -360,47 +366,126 @@
    10548     @ivar subscriptionFrom: Contact's subscription state. If C{True}, the
    10649                             contact is subscribed to the presence information
    10750                             of the roster owner.
    10851-    @type subscriptionTo: C{bool}
     52-    @ivar ask: Whether subscription is pending.
     53-    @type ask: C{bool}
    10954+    @type subscriptionFrom: C{bool}
    110      @ivar ask: Whether subscription is pending.
    111      @type ask: C{bool}
     55+    @ivar pendingOut: Whether the subscription request to this contact is
     56+        pending.
     57+    @type pendingOut: C{bool}
    11258     @ivar groups: Set of groups this contact is categorized in. Groups are
    113 @@ -596,13 +596,44 @@
     59                   represented by an opaque identifier of type C{unicode}.
    11460     @type groups: C{set}
    11561     """
    11662 
    11763-    def __init__(self, jid):
    118 +    subscriptionStates = {(False, False): 'none',
    119 +                          (True, False): 'to',
    120 +                          (False, True): 'from',
    121 +                          (True, True): 'both'}
    122 +
    123 +    def __init__(self, jid, subscriptionTo=False, subscriptionFrom=False,
    124 +                       ask=None, name=False, groups=None, remove=False):
    125          self.jid = jid
     64-        self.jid = jid
    12665-        self.name = None
    12766-        self.subscriptionTo = False
     
    12968-        self.ask = None
    13069-        self.groups = set()
     70+    __subscriptionStates = {(False, False): None,
     71+                            (True, False): 'to',
     72+                            (False, True): 'from',
     73+                            (True, True): 'both'}
     74 
     75+    def __init__(self, entity, subscriptionTo=False, subscriptionFrom=False,
     76+                       name=None, groups=None):
     77+        self.entity = entity
    13178+        self.subscriptionTo = subscriptionTo
    13279+        self.subscriptionFrom = subscriptionFrom
    133 +        self.ask = ask
    13480+        self.name = name
    13581+        self.groups = groups or set()
    136 +        self.remove = remove
     82 
     83+        self.pendingOut = False
     84+        self.approved = False
     85+        self.remove = False
     86 
     87-class RosterClientProtocol(XMPPHandler):
     88+
     89+    def __getJID(self):
     90+        warnings.warn("Use RosterItem.entity instead.", DeprecationWarning)
     91+        return self.entity
     92+
     93+
     94+    def __setJID(self, value):
     95+        warnings.warn("Use RosterItem.entity instead.", DeprecationWarning)
     96+        self.entity = value
     97+
     98+
     99+    jid = property(__getJID, __setJID, doc="""
     100+            JID of the contact. Deprecated in favour of C{entity}.""")
     101+
     102+
     103+    def __getAsk(self):
     104+        warnings.warn("Use RosterItem.pendingOut instead.", DeprecationWarning)
     105+        return self.pendingOut
     106+
     107+
     108+    def __setAsk(self, value):
     109+        warnings.warn("Use RosterItem.pendingOut instead.", DeprecationWarning)
     110+        self.pendingOut = value
     111+
     112+
     113+    ask = property(__getAsk, __setAsk, doc="""
     114+            Pending out subscription. Deprecated in favour of C{pendingOut}.""")
    137115+
    138116+
    139117+    def toElement(self):
    140118+        element = domish.Element((NS_ROSTER, 'item'))
    141 +        element['jid'] = self.jid.full()
     119+        element['jid'] = self.entity.full()
    142120+
    143121+        if self.remove:
    144122+            subscription = 'remove'
    145123+        else:
    146 +            subscription = self.subscriptionStates[self.subscriptionTo,
    147 +                                                   self.subscriptionFrom]
    148 +        element['subscription'] = subscription
    149 +
    150 +        if self.ask:
    151 +            element['ask'] = 'subscribe'
    152 +        if self.name:
    153 +            element['name'] = self.name
    154 +
    155 +        if self.groups:
    156 +            for group in self.groups:
    157 +                element.addElement('group', content=group)
     124+            subscription = self.__subscriptionStates[self.subscriptionTo,
     125+                                                     self.subscriptionFrom]
     126+
     127+            if self.pendingOut:
     128+                element['ask'] = u'subscribe'
     129+
     130+            if self.name:
     131+                element['name'] = self.name
     132+
     133+            if self.approved:
     134+                element['approved'] = u'true'
     135+
     136+            if self.groups:
     137+                for group in self.groups:
     138+                    element.addElement('group', content=group)
     139+
     140+        if subscription:
     141+            element['subscription'] = subscription
    158142+
    159143+        return element
    160144+
    161  
    162  
    163  class RosterClientProtocol(XMPPHandler):
    164 @@ -662,9 +693,8 @@
     145+
     146+    @classmethod
     147+    def fromElement(Class, element):
     148+        entity = internJID(element['jid'])
     149+        item = Class(entity)
     150+        subscription = element.getAttribute('subscription')
     151+        if subscription == 'remove':
     152+            item.remove = True
     153+        else:
     154+            item.name = element.getAttribute('name')
     155+            item.subscriptionTo = subscription in ('to', 'both')
     156+            item.subscriptionFrom = subscription in ('from', 'both')
     157+            item.pendingOut = element.getAttribute('ask') == 'subscribe'
     158+            item.approved = element.getAttribute('approved') in ('true', '1')
     159+            for subElement in domish.generateElementsQNamed(element.children,
     160+                                                            'group', NS_ROSTER):
     161+                item.groups.add(unicode(subElement))
     162+        return item
     163+
     164+
     165+
     166+class RosterPushIgnored(Exception):
     167+    """
     168+    Raised when this entity doesn't want to accept/trust a roster push.
     169+    """
     170+
     171+
     172+
     173+class RosterClientProtocol(XMPPHandler, IQHandlerMixin):
     174     """
     175     Client side XMPP roster protocol.
     176     """
     177 
     178+    iqHandlers = {XPATH_ROSTER_SET: "_onRosterSet"}
     179+
     180     def connectionInitialized(self):
     181-        ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER
     182-        self.xmlstream.addObserver(ROSTER_SET, self._onRosterSet)
     183-
     184-
     185-    def _parseRosterItem(self, element):
     186-        jid = internJID(element['jid'])
     187-        item = RosterItem(jid)
     188-        item.name = element.getAttribute('name')
     189-        subscription = element.getAttribute('subscription')
     190-        item.subscriptionTo = subscription in ('to', 'both')
     191-        item.subscriptionFrom = subscription in ('from', 'both')
     192-        item.ask = element.getAttribute('ask') == 'subscribe'
     193-        for subElement in domish.generateElementsQNamed(element.children,
     194-                                                        'group', NS_ROSTER):
     195-            item.groups.add(unicode(subElement))
     196-
     197-        return item
     198+        self.xmlstream.addObserver(XPATH_ROSTER_SET, self.handleRequest)
     199 
     200 
     201     def getRoster(self):
     202@@ -415,8 +500,8 @@
     203             roster = {}
     204             for element in domish.generateElementsQNamed(result.query.children,
     205                                                          'item', NS_ROSTER):
     206-                item = self._parseRosterItem(element)
     207-                roster[item.jid.userhost()] = item
     208+                item = RosterItem.fromElement(element)
     209+                roster[item.entity] = item
     210 
     211             return roster
     212 
     213@@ -437,32 +522,37 @@
    165214         """
    166215         iq = IQ(self.xmlstream, 'set')
     
    169218-        item['jid'] = entity.full()
    170219-        item['subscription'] = 'remove'
    171 +        item = RosterItem(entity, remove=True)
     220+        item = RosterItem(entity)
     221+        item.remove = True
    172222+        iq.query.addChild(item.toElement())
    173223         return iq.send()
    174224 
    175225 
     226     def _onRosterSet(self, iq):
     227-        if iq.handled or \
     228-           iq.hasAttribute('from') and iq['from'] != self.xmlstream:
     229-            return
     230-
     231-        iq.handled = True
     232+        def eb(failure):
     233+            failure.trap(RosterPushIgnored)
     234+            raise error.StanzaError('service-unavailable')
     235 
     236         itemElement = iq.query.item
     237+        item = RosterItem.fromElement(iq.query.item)
     238 
     239-        if unicode(itemElement['subscription']) == 'remove':
     240-            self.onRosterRemove(internJID(itemElement['jid']))
     241+        if item.remove:
     242+            d = defer.maybeDeferred(self.onRosterRemove, item.entity)
     243         else:
     244-            item = self._parseRosterItem(iq.query.item)
     245-            self.onRosterSet(item)
     246+            d = defer.maybeDeferred(self.onRosterSet, item)
     247+
     248+        d.addErrback(eb)
     249+        return d
     250 
     251 
     252     def onRosterSet(self, item):
     253         """
     254         Called when a roster push for a new or update item was received.
     255 
     256+        Raise L{RosterPushIgnored} when not accepting this roster push
     257+        (directly or via Deferred). This will result in a
     258+        L{'service-unavailable'} error being sent in return.
     259+
     260         @param item: The pushed roster item.
     261         @type item: L{RosterItem}
     262         """
     263@@ -472,6 +562,10 @@
     264         """
     265         Called when a roster push for the removal of an item was received.
     266 
     267+        Raise L{RosterPushIgnored} when not accepting this roster push
     268+        (directly or via Deferred). This will result in a
     269+        L{'service-unavailable'} error being sent in return.
     270+
     271         @param entity: The entity for which the roster item has been removed.
     272         @type entity: L{JID}
     273         """
     274diff -r c91f18811c37 wokkel/test/test_im.py
     275--- a/wokkel/test/test_im.py    Mon May 23 18:19:44 2011 +0200
     276+++ b/wokkel/test/test_im.py    Wed May 25 09:24:29 2011 +0200
     277@@ -7,13 +7,14 @@
     278 
     279 from twisted.internet import defer
     280 from twisted.trial import unittest
     281+from twisted.words.protocols.jabber import error
     282 from twisted.words.protocols.jabber.jid import JID
     283 from twisted.words.protocols.jabber.xmlstream import toResponse
     284 from twisted.words.xish import domish, utility
     285 
     286 from wokkel import im
     287 from wokkel.generic import ErrorStanza, parseXml
     288-from wokkel.test.helpers import XmlStreamStub
     289+from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub
     290 
     291 NS_XML = 'http://www.w3.org/XML/1998/namespace'
     292 NS_ROSTER = 'jabber:iq:roster'
     293@@ -389,23 +390,356 @@
     294 
     295 
     296 
     297-class RosterClientProtocolTest(unittest.TestCase):
     298+class RosterItemTest(unittest.TestCase):
     299+    """
     300+    Tests for L{im.RosterItem}.
     301+    """
     302+
     303+    def test_jidDeprecationGet(self):
     304+        """
     305+        Getting the jid attribute works as entity and warns deprecation.
     306+        """
     307+        item = im.RosterItem(JID('user@example.org'))
     308+        entity = self.assertWarns(DeprecationWarning,
     309+                                  "Use RosterItem.entity instead.",
     310+                                  im.__file__,
     311+                                  getattr, item, 'jid')
     312+        self.assertIdentical(entity, item.entity)
     313+
     314+
     315+    def test_jidDeprecationSet(self):
     316+        """
     317+        Setting the jid attribute works as entity and warns deprecation.
     318+        """
     319+        item = im.RosterItem(JID('user@example.org'))
     320+        entity = self.assertWarns(DeprecationWarning,
     321+                                  "Use RosterItem.entity instead.",
     322+                                  im.__file__,
     323+                                  setattr, item, 'jid',
     324+                                  JID('other@example.org'))
     325+        self.assertEquals(JID('other@example.org'), item.entity)
     326+
     327+
     328+    def test_askDeprecationGet(self):
     329+        """
     330+        Getting the ask attribute works as entity and warns deprecation.
     331+        """
     332+        item = im.RosterItem(JID('user@example.org'))
     333+        item.pendingOut = True
     334+        ask = self.assertWarns(DeprecationWarning,
     335+                               "Use RosterItem.pendingOut instead.",
     336+                               im.__file__,
     337+                               getattr, item, 'ask')
     338+        self.assertTrue(ask)
     339+
     340+
     341+    def test_askDeprecationSet(self):
     342+        """
     343+        Setting the ask attribute works as entity and warns deprecation.
     344+        """
     345+        item = im.RosterItem(JID('user@example.org'))
     346+        entity = self.assertWarns(DeprecationWarning,
     347+                                  "Use RosterItem.pendingOut instead.",
     348+                                  im.__file__,
     349+                                  setattr, item, 'ask',
     350+                                  True)
     351+        self.assertTrue(item.pendingOut)
     352+
     353+
     354+    def test_toElement(self):
     355+        item = im.RosterItem(JID('user@example.org'))
     356+        element = item.toElement()
     357+        self.assertEquals('item', element.name)
     358+        self.assertEquals(NS_ROSTER, element.uri)
     359+        self.assertFalse(element.hasAttribute('subscription'))
     360+        self.assertFalse(element.hasAttribute('ask'))
     361+        self.assertFalse(element.hasAttribute('name'))
     362+        self.assertFalse(element.hasAttribute('approved'))
     363+        self.assertEquals(0, len(list(element.elements())))
     364+
     365+
     366+    def test_toElementMinimal(self):
     367+        item = im.RosterItem(JID('user@example.org'))
     368+        element = item.toElement()
     369+        self.assertEquals(u'user@example.org', element.getAttribute('jid'))
     370+
     371+
     372+    def test_toElementSubscriptionNone(self):
     373+        item = im.RosterItem(JID('user@example.org'),
     374+                                 subscriptionTo=False,
     375+                                 subscriptionFrom=False)
     376+        element = item.toElement()
     377+        self.assertIdentical(None, element.getAttribute('subscription'))
     378+
     379+
     380+    def test_toElementSubscriptionTo(self):
     381+        item = im.RosterItem(JID('user@example.org'),
     382+                                 subscriptionTo=True,
     383+                                 subscriptionFrom=False)
     384+        element = item.toElement()
     385+        self.assertEquals('to', element.getAttribute('subscription'))
     386+
     387+
     388+    def test_toElementSubscriptionFrom(self):
     389+        item = im.RosterItem(JID('user@example.org'),
     390+                                 subscriptionTo=False,
     391+                                 subscriptionFrom=True)
     392+        element = item.toElement()
     393+        self.assertEquals('from', element.getAttribute('subscription'))
     394+
     395+
     396+    def test_toElementSubscriptionBoth(self):
     397+        item = im.RosterItem(JID('user@example.org'),
     398+                                 subscriptionTo=True,
     399+                                 subscriptionFrom=True)
     400+        element = item.toElement()
     401+        self.assertEquals('both', element.getAttribute('subscription'))
     402+
     403+
     404+    def test_toElementSubscriptionRemove(self):
     405+        item = im.RosterItem(JID('user@example.org'))
     406+        item.remove = True
     407+        element = item.toElement()
     408+        self.assertEquals('remove', element.getAttribute('subscription'))
     409+
     410+
     411+    def test_toElementAsk(self):
     412+        item = im.RosterItem(JID('user@example.org'))
     413+        item.pendingOut = True
     414+        element = item.toElement()
     415+        self.assertEquals('subscribe', element.getAttribute('ask'))
     416+
     417+
     418+    def test_toElementName(self):
     419+        item = im.RosterItem(JID('user@example.org'),
     420+                                 name='Joe User')
     421+        element = item.toElement()
     422+        self.assertEquals(u'Joe User', element.getAttribute('name'))
     423+
     424+
     425+    def test_toElementGroups(self):
     426+        groups = set(['Friends', 'Jabber'])
     427+        item = im.RosterItem(JID('user@example.org'),
     428+                                 groups=groups)
     429+
     430+        element = item.toElement()
     431+        foundGroups = set()
     432+        for child in element.elements():
     433+            if child.uri == NS_ROSTER and child.name == 'group':
     434+                foundGroups.add(unicode(child))
     435+
     436+        self.assertEqual(groups, foundGroups)
     437+
     438+
     439+    def test_toElementApproved(self):
     440+        """
     441+        A pre-approved subscription for a roster item has an 'approved' flag.
     442+        """
     443+        item = im.RosterItem(JID('user@example.org'))
     444+        item.approved = True
     445+        element = item.toElement()
     446+        self.assertEquals(u'true', element.getAttribute('approved'))
     447+
     448+
     449+    def test_fromElementMinimal(self):
     450+        """
     451+        A minimal roster item has a reference to the JID of the contact.
     452+        """
     453+
     454+        xml = """
     455+            <item xmlns="jabber:iq:roster"
     456+                  jid="test@example.org"/>
     457+        """
     458+
     459+        item = im.RosterItem.fromElement(parseXml(xml))
     460+        self.assertEqual(JID(u"test@example.org"), item.entity)
     461+        self.assertIdentical(None, item.name)
     462+        self.assertFalse(item.subscriptionTo)
     463+        self.assertFalse(item.subscriptionFrom)
     464+        self.assertFalse(item.pendingOut)
     465+        self.assertFalse(item.approved)
     466+        self.assertEquals(set(), item.groups)
     467+
     468+
     469+    def test_fromElementName(self):
     470+        """
     471+        A roster item may have an optional name.
     472+        """
     473+
     474+        xml = """
     475+            <item xmlns="jabber:iq:roster"
     476+                  jid="test@example.org"
     477+                  name="Test User"/>
     478+        """
     479+
     480+        item = im.RosterItem.fromElement(parseXml(xml))
     481+        self.assertEqual(u"Test User", item.name)
     482+
     483+
     484+    def test_fromElementGroups(self):
     485+        """
     486+        A roster item may have one or more groups.
     487+        """
     488+
     489+        xml = """
     490+            <item xmlns="jabber:iq:roster"
     491+                  jid="test@example.org">
     492+              <group>Friends</group>
     493+              <group>Twisted</group>
     494+            </item>
     495+        """
     496+
     497+        item = im.RosterItem.fromElement(parseXml(xml))
     498+        self.assertIn(u"Twisted", item.groups)
     499+        self.assertIn(u"Friends", item.groups)
     500+
     501+
     502+    def test_fromElementSubscriptionNone(self):
     503+        """
     504+        Subscription 'none' sets both attributes to False.
     505+        """
     506+
     507+        xml = """
     508+            <item xmlns="jabber:iq:roster"
     509+                  jid="test@example.org"
     510+                  subscription="none"/>
     511+        """
     512+
     513+        item = im.RosterItem.fromElement(parseXml(xml))
     514+        self.assertFalse(item.remove)
     515+        self.assertFalse(item.subscriptionTo)
     516+        self.assertFalse(item.subscriptionFrom)
     517+
     518+
     519+    def test_fromElementSubscriptionTo(self):
     520+        """
     521+        Subscription 'to' sets the corresponding attribute to True.
     522+        """
     523+
     524+        xml = """
     525+            <item xmlns="jabber:iq:roster"
     526+                  jid="test@example.org"
     527+                  subscription="to"/>
     528+        """
     529+
     530+        item = im.RosterItem.fromElement(parseXml(xml))
     531+        self.assertFalse(item.remove)
     532+        self.assertTrue(item.subscriptionTo)
     533+        self.assertFalse(item.subscriptionFrom)
     534+
     535+
     536+    def test_fromElementSubscriptionFrom(self):
     537+        """
     538+        Subscription 'from' sets the corresponding attribute to True.
     539+        """
     540+
     541+        xml = """
     542+            <item xmlns="jabber:iq:roster"
     543+                  jid="test@example.org"
     544+                  subscription="from"/>
     545+        """
     546+
     547+        item = im.RosterItem.fromElement(parseXml(xml))
     548+        self.assertFalse(item.remove)
     549+        self.assertFalse(item.subscriptionTo)
     550+        self.assertTrue(item.subscriptionFrom)
     551+
     552+
     553+    def test_fromElementSubscriptionBoth(self):
     554+        """
     555+        Subscription 'both' sets both attributes to True.
     556+        """
     557+
     558+        xml = """
     559+            <item xmlns="jabber:iq:roster"
     560+                  jid="test@example.org"
     561+                  subscription="both"/>
     562+        """
     563+
     564+        item = im.RosterItem.fromElement(parseXml(xml))
     565+        self.assertFalse(item.remove)
     566+        self.assertTrue(item.subscriptionTo)
     567+        self.assertTrue(item.subscriptionFrom)
     568+
     569+
     570+    def test_fromElementSubscriptionRemove(self):
     571+        """
     572+        Subscription 'remove' sets the remove attribute.
     573+        """
     574+
     575+        xml = """
     576+            <item xmlns="jabber:iq:roster"
     577+                  jid="test@example.org"
     578+                  subscription="remove"/>
     579+        """
     580+
     581+        item = im.RosterItem.fromElement(parseXml(xml))
     582+        self.assertTrue(item.remove)
     583+
     584+
     585+    def test_fromElementPendingOut(self):
     586+        """
     587+        The ask attribute, if set to 'subscription', means pending out.
     588+        """
     589+
     590+        xml = """
     591+            <item xmlns="jabber:iq:roster"
     592+                  jid="test@example.org"
     593+                  ask="subscribe"/>
     594+        """
     595+
     596+        item = im.RosterItem.fromElement(parseXml(xml))
     597+        self.assertTrue(item.pendingOut)
     598+
     599+
     600+    def test_fromElementApprovedTrue(self):
     601+        """
     602+        The approved attribute (true) signals a pre-approved subscription.
     603+        """
     604+
     605+        xml = """
     606+            <item xmlns="jabber:iq:roster"
     607+                  jid="test@example.org"
     608+                  approved="true"/>
     609+        """
     610+
     611+        item = im.RosterItem.fromElement(parseXml(xml))
     612+        self.assertTrue(item.approved)
     613+
     614+
     615+    def test_fromElementApproved1(self):
     616+        """
     617+        The approved attribute (1) signals a pre-approved subscription.
     618+        """
     619+
     620+        xml = """
     621+            <item xmlns="jabber:iq:roster"
     622+                  jid="test@example.org"
     623+                  approved="1"/>
     624+        """
     625+
     626+        item = im.RosterItem.fromElement(parseXml(xml))
     627+        self.assertTrue(item.approved)
     628+
     629+
     630+
     631+class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
     632     """
     633     Tests for L{im.RosterClientProtocol}.
     634     """
     635 
     636     def setUp(self):
     637         self.stub = XmlStreamStub()
     638-        self.protocol = im.RosterClientProtocol()
     639-        self.protocol.xmlstream = self.stub.xmlstream
     640-        self.protocol.connectionInitialized()
     641+        self.service = im.RosterClientProtocol()
     642+        self.service.makeConnection(self.stub.xmlstream)
     643+        self.service.connectionInitialized()
     644 
     645 
     646     def test_removeItem(self):
     647         """
     648         Removing a roster item is setting an item with subscription C{remove}.
     649         """
     650-        d = self.protocol.removeItem(JID('test@example.org'))
     651+        d = self.service.removeItem(JID('test@example.org'))
     652 
     653         # Inspect outgoing iq request
     654 
     655@@ -419,10 +753,117 @@
     656         self.assertEquals(1, len(children))
     657         child = children[0]
     658         self.assertEquals('test@example.org', child['jid'])
     659-        self.assertEquals('remove', child['subscription'])
     660+        self.assertEquals('remove', child.getAttribute('subscription'))
     661 
     662         # Fake successful response
     663 
     664         response = toResponse(iq, 'result')
     665         self.stub.send(response)
     666         return d
     667+
     668+
     669+    def test_getRoster(self):
     670+        def cb(roster):
     671+            self.assertIn(JID('user@example.org'), roster)
     672+
     673+
     674+        d = self.service.getRoster()
     675+        d.addCallback(cb)
     676+
     677+        # Inspect outgoing iq request
     678+
     679+        iq = self.stub.output[-1]
     680+        self.assertEquals('get', iq.getAttribute('type'))
     681+        self.assertNotIdentical(None, iq.query)
     682+        self.assertEquals(NS_ROSTER, iq.query.uri)
     683+
     684+        # Fake successful response
     685+        response = toResponse(iq, 'result')
     686+        query = response.addElement((NS_ROSTER, 'query'))
     687+        item = query.addElement('item')
     688+        item['jid'] = 'user@example.org'
     689+
     690+        self.stub.send(response)
     691+        return d
     692+
     693+
     694+    def test_onRosterSet(self):
     695+        """
     696+        A roster push causes onRosterSet to be called with the parsed item.
     697+        """
     698+        xml = """
     699+          <iq type='set'>
     700+            <query xmlns='jabber:iq:roster'>
     701+              <item jid='user@example.org'/>
     702+            </query>
     703+          </iq>
     704+        """
     705+
     706+        items = []
     707+
     708+        def onRosterSet(item):
     709+            items.append(item)
     710+
     711+        def cb(result):
     712+            self.assertEquals(1, len(items))
     713+            self.assertEquals(JID('user@example.org'), items[0].entity)
     714+
     715+        self.service.onRosterSet = onRosterSet
     716+
     717+        d = self.handleRequest(xml)
     718+        d.addCallback(cb)
     719+        return d
     720+
     721+
     722+    def test_onRosterSetUntrusted(self):
     723+        """
     724+        Roster pushes from untrusted sources will be not be handled.
     725+        """
     726+        xml = """
     727+          <iq type='set' from='bad@example.org'>
     728+            <query xmlns='jabber:iq:roster'>
     729+              <item jid='user@example.org'/>
     730+            </query>
     731+          </iq>
     732+        """
     733+
     734+        def onRosterSet(item):
     735+            raise im.RosterPushIgnored()
     736+
     737+        def cb(result):
     738+            self.assertEquals('service-unavailable', result.condition)
     739+
     740+        self.service.onRosterSet = onRosterSet
     741+
     742+        d = self.handleRequest(xml)
     743+        self.assertFailure(d, error.StanzaError)
     744+        d.addCallback(cb)
     745+        return d
     746+
     747+
     748+    def test_onRosterRemove(self):
     749+        """
     750+        A roster push causes onRosterSet to be called with the parsed item.
     751+        """
     752+        xml = """
     753+          <iq type='set'>
     754+            <query xmlns='jabber:iq:roster'>
     755+              <item jid='user@example.org' subscription='remove'/>
     756+            </query>
     757+          </iq>
     758+        """
     759+
     760+        entities = []
     761+
     762+        def onRosterRemove(entity):
     763+            entities.append(entity)
     764+
     765+        def cb(result):
     766+            self.assertEquals([JID('user@example.org')], entities)
     767+
     768+        self.service.onRosterRemove = onRosterRemove
     769+
     770+        d = self.handleRequest(xml)
     771+        d.addCallback(cb)
     772+        return d
     773+
Note: See TracChangeset for help on using the changeset viewer.