Ignore:
Timestamp:
May 9, 2012, 2:24:28 PM (11 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Message:

Clean up of RosterItem? and RosterClientProtocol?.

RosterItem:

  • Renamed attributes jid and ask to entity and pendingOut respectively.
  • Can represent roster items to be removed or that have been removed.
  • Now has fromElement and toElement methods.

RosterRequest is a new class to represent roster request stanzas.

RosterClientProtocol:

  • Roster returned from getRoster is now indexed by JIDs (instead of the unicode representation of the JID).
  • Outgoing requests are now done using RosterRequest.
  • onRosterSet and onRosterRemove are deprecated in favor of setReceived and removeReceived, respectively. These are called with a RosterRequest to have access to addressing and roster version information.

RosterPushIgnored can be raised to return a service-unavailable stanza
error for unwanted pushes.

This also fixes a problem with checking the sender address for roster pushes.

Author: ralphm.
Fixes: #71.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • wokkel/test/test_xmppim.py

    r96 r172  
    88from twisted.internet import defer
    99from twisted.trial import unittest
     10from twisted.words.protocols.jabber import error
    1011from twisted.words.protocols.jabber.jid import JID
    1112from twisted.words.protocols.jabber.xmlstream import toResponse
     
    1415from wokkel import xmppim
    1516from wokkel.generic import ErrorStanza, parseXml
    16 from wokkel.test.helpers import XmlStreamStub
     17from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub
    1718
    1819NS_XML = 'http://www.w3.org/XML/1998/namespace'
     
    450451
    451452
    452 class RosterClientProtocolTest(unittest.TestCase):
     453class RosterItemTest(unittest.TestCase):
     454    """
     455    Tests for L{xmppim.RosterItem}.
     456    """
     457
     458    def test_toElement(self):
     459        """
     460        A roster item has the correct namespace/name, lacks unset attributes.
     461        """
     462        item = xmppim.RosterItem(JID('user@example.org'))
     463        element = item.toElement()
     464        self.assertEqual('item', element.name)
     465        self.assertEqual(NS_ROSTER, element.uri)
     466        self.assertFalse(element.hasAttribute('subscription'))
     467        self.assertFalse(element.hasAttribute('ask'))
     468        self.assertEqual(u"", element.getAttribute('name', u""))
     469        self.assertFalse(element.hasAttribute('approved'))
     470        self.assertEquals(0, len(list(element.elements())))
     471
     472
     473    def test_toElementMinimal(self):
     474        """
     475        A bare roster item only has a jid attribute.
     476        """
     477        item = xmppim.RosterItem(JID('user@example.org'))
     478        element = item.toElement()
     479        self.assertEqual(u'user@example.org', element.getAttribute('jid'))
     480
     481
     482    def test_toElementSubscriptionNone(self):
     483        """
     484        A roster item with no subscription has no subscription attribute.
     485        """
     486        item = xmppim.RosterItem(JID('user@example.org'),
     487                                 subscriptionTo=False,
     488                                 subscriptionFrom=False)
     489        element = item.toElement()
     490        self.assertIdentical(None, element.getAttribute('subscription'))
     491
     492
     493    def test_toElementSubscriptionTo(self):
     494        """
     495        A roster item with subscriptionTo set has subscription 'to'.
     496        """
     497        item = xmppim.RosterItem(JID('user@example.org'),
     498                                 subscriptionTo=True,
     499                                 subscriptionFrom=False)
     500        element = item.toElement()
     501        self.assertEqual('to', element.getAttribute('subscription'))
     502
     503
     504    def test_toElementSubscriptionFrom(self):
     505        """
     506        A roster item with subscriptionFrom set has subscription 'to'.
     507        """
     508        item = xmppim.RosterItem(JID('user@example.org'),
     509                                 subscriptionTo=False,
     510                                 subscriptionFrom=True)
     511        element = item.toElement()
     512        self.assertEqual('from', element.getAttribute('subscription'))
     513
     514
     515    def test_toElementSubscriptionBoth(self):
     516        """
     517        A roster item with mutual subscription has subscription 'both'.
     518        """
     519        item = xmppim.RosterItem(JID('user@example.org'),
     520                                 subscriptionTo=True,
     521                                 subscriptionFrom=True)
     522        element = item.toElement()
     523        self.assertEqual('both', element.getAttribute('subscription'))
     524
     525
     526    def test_toElementSubscriptionRemove(self):
     527        """
     528        A roster item with remove set has subscription 'remove'.
     529        """
     530        item = xmppim.RosterItem(JID('user@example.org'))
     531        item.remove = True
     532        element = item.toElement()
     533        self.assertEqual('remove', element.getAttribute('subscription'))
     534
     535
     536    def test_toElementAsk(self):
     537        """
     538        A roster item with pendingOut set has subscription 'ask'.
     539        """
     540        item = xmppim.RosterItem(JID('user@example.org'))
     541        item.pendingOut = True
     542        element = item.toElement()
     543        self.assertEqual('subscribe', element.getAttribute('ask'))
     544
     545
     546    def test_toElementName(self):
     547        """
     548        A roster item's name is rendered to the 'name' attribute.
     549        """
     550        item = xmppim.RosterItem(JID('user@example.org'),
     551                                 name='Joe User')
     552        element = item.toElement()
     553        self.assertEqual(u'Joe User', element.getAttribute('name'))
     554
     555
     556    def test_toElementGroups(self):
     557        """
     558        A roster item's groups are rendered as 'group' child elements.
     559        """
     560        groups = set(['Friends', 'Jabber'])
     561        item = xmppim.RosterItem(JID('user@example.org'),
     562                                 groups=groups)
     563
     564        element = item.toElement()
     565        foundGroups = set()
     566        for child in element.elements():
     567            if child.uri == NS_ROSTER and child.name == 'group':
     568                foundGroups.add(unicode(child))
     569
     570        self.assertEqual(groups, foundGroups)
     571
     572
     573    def test_toElementApproved(self):
     574        """
     575        A pre-approved subscription for a roster item has an 'approved' flag.
     576        """
     577        item = xmppim.RosterItem(JID('user@example.org'))
     578        item.approved = True
     579        element = item.toElement()
     580        self.assertEqual(u'true', element.getAttribute('approved'))
     581
     582
     583    def test_fromElementMinimal(self):
     584        """
     585        A minimal roster item has a reference to the JID of the contact.
     586        """
     587
     588        xml = """
     589            <item xmlns="jabber:iq:roster"
     590                  jid="test@example.org"/>
     591        """
     592
     593        item = xmppim.RosterItem.fromElement(parseXml(xml))
     594        self.assertEqual(JID(u"test@example.org"), item.entity)
     595        self.assertEqual(u"", item.name)
     596        self.assertFalse(item.subscriptionTo)
     597        self.assertFalse(item.subscriptionFrom)
     598        self.assertFalse(item.pendingOut)
     599        self.assertFalse(item.approved)
     600        self.assertEqual(set(), item.groups)
     601
     602
     603    def test_fromElementName(self):
     604        """
     605        A roster item may have an optional name.
     606        """
     607
     608        xml = """
     609            <item xmlns="jabber:iq:roster"
     610                  jid="test@example.org"
     611                  name="Test User"/>
     612        """
     613
     614        item = xmppim.RosterItem.fromElement(parseXml(xml))
     615        self.assertEqual(u"Test User", item.name)
     616
     617
     618    def test_fromElementGroups(self):
     619        """
     620        A roster item may have one or more groups.
     621        """
     622
     623        xml = """
     624            <item xmlns="jabber:iq:roster"
     625                  jid="test@example.org">
     626              <group>Friends</group>
     627              <group>Twisted</group>
     628            </item>
     629        """
     630
     631        item = xmppim.RosterItem.fromElement(parseXml(xml))
     632        self.assertIn(u"Twisted", item.groups)
     633        self.assertIn(u"Friends", item.groups)
     634
     635
     636    def test_fromElementSubscriptionNone(self):
     637        """
     638        Subscription 'none' sets both attributes to False.
     639        """
     640
     641        xml = """
     642            <item xmlns="jabber:iq:roster"
     643                  jid="test@example.org"
     644                  subscription="none"/>
     645        """
     646
     647        item = xmppim.RosterItem.fromElement(parseXml(xml))
     648        self.assertFalse(item.remove)
     649        self.assertFalse(item.subscriptionTo)
     650        self.assertFalse(item.subscriptionFrom)
     651
     652
     653    def test_fromElementSubscriptionTo(self):
     654        """
     655        Subscription 'to' sets the corresponding attribute to True.
     656        """
     657
     658        xml = """
     659            <item xmlns="jabber:iq:roster"
     660                  jid="test@example.org"
     661                  subscription="to"/>
     662        """
     663
     664        item = xmppim.RosterItem.fromElement(parseXml(xml))
     665        self.assertFalse(item.remove)
     666        self.assertTrue(item.subscriptionTo)
     667        self.assertFalse(item.subscriptionFrom)
     668
     669
     670    def test_fromElementSubscriptionFrom(self):
     671        """
     672        Subscription 'from' sets the corresponding attribute to True.
     673        """
     674
     675        xml = """
     676            <item xmlns="jabber:iq:roster"
     677                  jid="test@example.org"
     678                  subscription="from"/>
     679        """
     680
     681        item = xmppim.RosterItem.fromElement(parseXml(xml))
     682        self.assertFalse(item.remove)
     683        self.assertFalse(item.subscriptionTo)
     684        self.assertTrue(item.subscriptionFrom)
     685
     686
     687    def test_fromElementSubscriptionBoth(self):
     688        """
     689        Subscription 'both' sets both attributes to True.
     690        """
     691
     692        xml = """
     693            <item xmlns="jabber:iq:roster"
     694                  jid="test@example.org"
     695                  subscription="both"/>
     696        """
     697
     698        item = xmppim.RosterItem.fromElement(parseXml(xml))
     699        self.assertFalse(item.remove)
     700        self.assertTrue(item.subscriptionTo)
     701        self.assertTrue(item.subscriptionFrom)
     702
     703
     704    def test_fromElementSubscriptionRemove(self):
     705        """
     706        Subscription 'remove' sets the remove attribute.
     707        """
     708
     709        xml = """
     710            <item xmlns="jabber:iq:roster"
     711                  jid="test@example.org"
     712                  subscription="remove"/>
     713        """
     714
     715        item = xmppim.RosterItem.fromElement(parseXml(xml))
     716        self.assertTrue(item.remove)
     717
     718
     719    def test_fromElementPendingOut(self):
     720        """
     721        The ask attribute, if set to 'subscription', means pending out.
     722        """
     723
     724        xml = """
     725            <item xmlns="jabber:iq:roster"
     726                  jid="test@example.org"
     727                  ask="subscribe"/>
     728        """
     729
     730        item = xmppim.RosterItem.fromElement(parseXml(xml))
     731        self.assertTrue(item.pendingOut)
     732
     733
     734    def test_fromElementApprovedTrue(self):
     735        """
     736        The approved attribute (true) signals a pre-approved subscription.
     737        """
     738
     739        xml = """
     740            <item xmlns="jabber:iq:roster"
     741                  jid="test@example.org"
     742                  approved="true"/>
     743        """
     744
     745        item = xmppim.RosterItem.fromElement(parseXml(xml))
     746        self.assertTrue(item.approved)
     747
     748
     749    def test_fromElementApproved1(self):
     750        """
     751        The approved attribute (1) signals a pre-approved subscription.
     752        """
     753
     754        xml = """
     755            <item xmlns="jabber:iq:roster"
     756                  jid="test@example.org"
     757                  approved="1"/>
     758        """
     759
     760        item = xmppim.RosterItem.fromElement(parseXml(xml))
     761        self.assertTrue(item.approved)
     762
     763
     764    def test_jidDeprecationGet(self):
     765        """
     766        Getting the jid attribute works as entity and warns deprecation.
     767        """
     768        item = xmppim.RosterItem(JID('user@example.org'))
     769        entity = self.assertWarns(DeprecationWarning,
     770                                  "wokkel.xmppim.RosterItem.jid is deprecated. "
     771                                  "Use RosterItem.entity instead.",
     772                                  xmppim.__file__,
     773                                  getattr, item, 'jid')
     774        self.assertIdentical(entity, item.entity)
     775
     776
     777    def test_jidDeprecationSet(self):
     778        """
     779        Setting the jid attribute works as entity and warns deprecation.
     780        """
     781        item = xmppim.RosterItem(JID('user@example.org'))
     782        self.assertWarns(DeprecationWarning,
     783                         "wokkel.xmppim.RosterItem.jid is deprecated. "
     784                         "Use RosterItem.entity instead.",
     785                         xmppim.__file__,
     786                         setattr, item, 'jid',
     787                         JID('other@example.org'))
     788        self.assertEqual(JID('other@example.org'), item.entity)
     789
     790
     791    def test_askDeprecationGet(self):
     792        """
     793        Getting the ask attribute works as entity and warns deprecation.
     794        """
     795        item = xmppim.RosterItem(JID('user@example.org'))
     796        item.pendingOut = True
     797        ask = self.assertWarns(DeprecationWarning,
     798                               "wokkel.xmppim.RosterItem.ask is deprecated. "
     799                               "Use RosterItem.pendingOut instead.",
     800                               xmppim.__file__,
     801                               getattr, item, 'ask')
     802        self.assertTrue(ask)
     803
     804
     805    def test_askDeprecationSet(self):
     806        """
     807        Setting the ask attribute works as entity and warns deprecation.
     808        """
     809        item = xmppim.RosterItem(JID('user@example.org'))
     810        self.assertWarns(DeprecationWarning,
     811                         "wokkel.xmppim.RosterItem.ask is deprecated. "
     812                         "Use RosterItem.pendingOut instead.",
     813                         xmppim.__file__,
     814                         setattr, item, 'ask',
     815                         True)
     816        self.assertTrue(item.pendingOut)
     817
     818
     819
     820class RosterRequestTest(unittest.TestCase):
     821    """
     822    Tests for L{xmppim.RosterRequest}.
     823    """
     824
     825    def test_fromElement(self):
     826        """
     827        A bare roster request is parsed and missing information is None.
     828        """
     829        xml = """
     830            <iq type='get' to='this@example.org/Home' from='this@example.org'>
     831              <query xmlns='jabber:iq:roster'/>
     832            </iq>
     833        """
     834
     835        request = xmppim.RosterRequest.fromElement(parseXml(xml))
     836        self.assertEqual('get', request.stanzaType)
     837        self.assertEqual(JID('this@example.org/Home'), request.recipient)
     838        self.assertEqual(JID('this@example.org'), request.sender)
     839        self.assertEqual(None, request.item)
     840
     841
     842    def test_fromElementItem(self):
     843        """
     844        If an item is present, parse it and put it in the request item.
     845        """
     846        xml = """
     847            <iq type='set' to='this@example.org/Home' from='this@example.org'>
     848              <query xmlns='jabber:iq:roster'>
     849                <item jid='user@example.org'/>
     850              </query>
     851            </iq>
     852        """
     853
     854        request = xmppim.RosterRequest.fromElement(parseXml(xml))
     855        self.assertNotIdentical(None, request.item)
     856        self.assertEqual(JID('user@example.org'), request.item.entity)
     857
     858
     859    def test_toElement(self):
     860        """
     861        A roster request has a query element in the roster namespace.
     862        """
     863        request = xmppim.RosterRequest()
     864        element = request.toElement()
     865        children = element.elements()
     866        child = children.next()
     867        self.assertEqual(NS_ROSTER, child.uri)
     868        self.assertEqual('query', child.name)
     869
     870
     871    def test_toElementItem(self):
     872        """
     873        If an item is set, it is rendered as a child of the query.
     874        """
     875        request = xmppim.RosterRequest()
     876        request.item = xmppim.RosterItem(JID('user@example.org'))
     877        element = request.toElement()
     878        children = element.query.elements()
     879        child = children.next()
     880        self.assertEqual(NS_ROSTER, child.uri)
     881        self.assertEqual('item', child.name)
     882
     883
     884
     885class FakeClient(object):
     886    """
     887    Fake client stream manager for roster tests.
     888    """
     889
     890    def __init__(self, xmlstream, jid):
     891        self.xmlstream = xmlstream
     892        self.jid = jid
     893
     894
     895    def request(self, request):
     896        element = request.toElement()
     897        self.xmlstream.send(element)
     898        return defer.Deferred()
     899
     900
     901    def addHandler(self, handler):
     902        handler.makeConnection(self.xmlstream)
     903        handler.connectionInitialized()
     904
     905
     906
     907class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
    453908    """
    454909    Tests for L{xmppim.RosterClientProtocol}.
     
    457912    def setUp(self):
    458913        self.stub = XmlStreamStub()
    459         self.protocol = xmppim.RosterClientProtocol()
    460         self.protocol.xmlstream = self.stub.xmlstream
    461         self.protocol.connectionInitialized()
     914        self.client = FakeClient(self.stub.xmlstream, JID('this@example.org'))
     915        self.service = xmppim.RosterClientProtocol()
     916        self.service.setHandlerParent(self.client)
    462917
    463918
     
    466921        Removing a roster item is setting an item with subscription C{remove}.
    467922        """
    468         d = self.protocol.removeItem(JID('test@example.org'))
     923        d = self.service.removeItem(JID('test@example.org'))
    469924
    470925        # Inspect outgoing iq request
    471926
    472927        iq = self.stub.output[-1]
    473         self.assertEquals('set', iq.getAttribute('type'))
     928        self.assertEqual('set', iq.getAttribute('type'))
    474929        self.assertNotIdentical(None, iq.query)
    475         self.assertEquals(NS_ROSTER, iq.query.uri)
     930        self.assertEqual(NS_ROSTER, iq.query.uri)
    476931
    477932        children = list(domish.generateElementsQNamed(iq.query.children,
    478933                                                      'item', NS_ROSTER))
    479         self.assertEquals(1, len(children))
     934        self.assertEqual(1, len(children))
    480935        child = children[0]
    481         self.assertEquals('test@example.org', child['jid'])
    482         self.assertEquals('remove', child['subscription'])
     936        self.assertEqual('test@example.org', child['jid'])
     937        self.assertEqual('remove', child.getAttribute('subscription'))
    483938
    484939        # Fake successful response
    485940
    486941        response = toResponse(iq, 'result')
    487         self.stub.send(response)
    488         return d
     942        d.callback(response)
     943        return d
     944
     945
     946    def test_getRoster(self):
     947        """
     948        A request for the roster is sent out and the response is parsed.
     949        """
     950        def cb(roster):
     951            self.assertIn(JID('user@example.org'), roster)
     952
     953        d = self.service.getRoster()
     954        d.addCallback(cb)
     955
     956        # Inspect outgoing iq request
     957
     958        iq = self.stub.output[-1]
     959        self.assertEqual('get', iq.getAttribute('type'))
     960        self.assertNotIdentical(None, iq.query)
     961        self.assertEqual(NS_ROSTER, iq.query.uri)
     962
     963        # Fake successful response
     964        response = toResponse(iq, 'result')
     965        query = response.addElement((NS_ROSTER, 'query'))
     966        item = query.addElement('item')
     967        item['jid'] = 'user@example.org'
     968
     969        d.callback(response)
     970        return d
     971
     972
     973    def test_onRosterSet(self):
     974        """
     975        A roster push causes onRosterSet to be called with the parsed item.
     976        """
     977        xml = """
     978          <iq type='set'>
     979            <query xmlns='jabber:iq:roster'>
     980              <item jid='user@example.org'/>
     981            </query>
     982          </iq>
     983        """
     984
     985        items = []
     986
     987        def onRosterSet(item):
     988            items.append(item)
     989
     990        def cb(result):
     991            self.assertEqual(1, len(items))
     992            self.assertEqual(JID('user@example.org'), items[0].entity)
     993
     994        self.service.onRosterSet = onRosterSet
     995
     996        d = self.assertWarns(DeprecationWarning,
     997                             "wokkel.xmppim.RosterClientProtocol.onRosterSet "
     998                             "is deprecated. "
     999                             "Use RosterClientProtocol.setReceived instead.",
     1000                             xmppim.__file__,
     1001                             self.handleRequest, xml)
     1002        d.addCallback(cb)
     1003        return d
     1004
     1005
     1006    def test_onRosterRemove(self):
     1007        """
     1008        A roster push causes onRosterSet to be called with the parsed item.
     1009        """
     1010        xml = """
     1011          <iq type='set'>
     1012            <query xmlns='jabber:iq:roster'>
     1013              <item jid='user@example.org' subscription='remove'/>
     1014            </query>
     1015          </iq>
     1016        """
     1017
     1018        entities = []
     1019
     1020        def onRosterRemove(entity):
     1021            entities.append(entity)
     1022
     1023        def cb(result):
     1024            self.assertEqual([JID('user@example.org')], entities)
     1025
     1026        self.service.onRosterRemove = onRosterRemove
     1027
     1028        d = self.assertWarns(DeprecationWarning,
     1029                             "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
     1030                             "is deprecated. "
     1031                             "Use RosterClientProtocol.removeReceived instead.",
     1032                             xmppim.__file__,
     1033                             self.handleRequest, xml)
     1034        d.addCallback(cb)
     1035        return d
     1036
     1037
     1038    def test_setReceived(self):
     1039        """
     1040        A roster set push causes setReceived.
     1041        """
     1042        xml = """
     1043          <iq type='set'>
     1044            <query xmlns='jabber:iq:roster'>
     1045              <item jid='user@example.org'/>
     1046            </query>
     1047          </iq>
     1048        """
     1049
     1050        requests = []
     1051
     1052        def setReceived(request):
     1053            requests.append(request)
     1054
     1055        def cb(result):
     1056            self.assertEqual(1, len(requests), "setReceived was not called")
     1057            self.assertEqual(JID('user@example.org'), requests[0].item.entity)
     1058
     1059        self.service.setReceived = setReceived
     1060
     1061        d = self.handleRequest(xml)
     1062        d.addCallback(cb)
     1063        return d
     1064
     1065
     1066    def test_setReceivedOtherSource(self):
     1067        """
     1068        Roster pushes can be sent from other entities, too, ignore them.
     1069        """
     1070        xml = """
     1071          <iq type='set' to='this@example.org/Home' from='other@example.org'>
     1072            <query xmlns='jabber:iq:roster'>
     1073              <item jid='user@example.org'/>
     1074            </query>
     1075          </iq>
     1076        """
     1077
     1078        def cb(result):
     1079            self.assertEquals('service-unavailable', result.condition)
     1080
     1081        d = self.handleRequest(xml)
     1082        self.assertFailure(d, error.StanzaError)
     1083        d.addCallback(cb)
     1084        return d
     1085
     1086
     1087    def test_setReceivedOtherSourceAllowed(self):
     1088        """
     1089        Roster pushes can be sent from other entities, allow them.
     1090        """
     1091        xml = """
     1092          <iq type='set' to='this@example.org/Home' from='other@example.org'>
     1093            <query xmlns='jabber:iq:roster'>
     1094              <item jid='user@example.org'/>
     1095            </query>
     1096          </iq>
     1097        """
     1098
     1099        self.service.allowAnySender = True
     1100        requests = []
     1101
     1102        def setReceived(request):
     1103            requests.append(request)
     1104
     1105        def cb(result):
     1106            self.assertEqual(1, len(requests), "setReceived was not called")
     1107
     1108        self.service.setReceived = setReceived
     1109
     1110        d = self.handleRequest(xml)
     1111        d.addCallback(cb)
     1112        return d
     1113
     1114
     1115    def test_setReceivedOtherSourceIgnored(self):
     1116        """
     1117        Roster pushes can be sent from other entities, allow them.
     1118        """
     1119        xml = """
     1120          <iq type='set' to='this@example.org/Home' from='bad@example.org'>
     1121            <query xmlns='jabber:iq:roster'>
     1122              <item jid='user@example.org'/>
     1123            </query>
     1124          </iq>
     1125        """
     1126
     1127        self.service.allowAnySender = True
     1128
     1129        def setReceived(request):
     1130            if request.sender == JID('bad@example.org'):
     1131                raise xmppim.RosterPushIgnored()
     1132
     1133        def cb(result):
     1134            self.assertEquals('service-unavailable', result.condition)
     1135
     1136        self.service.setReceived = setReceived
     1137
     1138
     1139        d = self.handleRequest(xml)
     1140        self.assertFailure(d, error.StanzaError)
     1141        d.addCallback(cb)
     1142        return d
     1143
     1144
     1145    def test_removeReceived(self):
     1146        """
     1147        A roster remove push causes removeReceived.
     1148        """
     1149        xml = """
     1150          <iq type='set'>
     1151            <query xmlns='jabber:iq:roster'>
     1152              <item jid='user@example.org' subscription='remove'/>
     1153            </query>
     1154          </iq>
     1155        """
     1156
     1157        requests = []
     1158
     1159        def removeReceived(request):
     1160            requests.append(request)
     1161
     1162        def cb(result):
     1163            self.assertEqual(1, len(requests), "removeReceived was not called")
     1164            self.assertEqual(JID('user@example.org'), requests[0].item.entity)
     1165
     1166        self.service.removeReceived = removeReceived
     1167
     1168        d = self.handleRequest(xml)
     1169        d.addCallback(cb)
     1170        return d
Note: See TracChangeset for help on using the changeset viewer.