Home | History | Annotate | Download | only in xmpp
      1 /*
      2  * libjingle
      3  * Copyright 2004, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include <string>
     29 #include <sstream>
     30 #include <iostream>
     31 
     32 #include "talk/base/gunit.h"
     33 #include "talk/base/scoped_ptr.h"
     34 #include "talk/xmllite/xmlelement.h"
     35 #include "talk/xmpp/xmppengine.h"
     36 #include "talk/xmpp/rostermodule.h"
     37 #include "talk/xmpp/constants.h"
     38 #include "talk/xmpp/util_unittest.h"
     39 
     40 #define TEST_OK(x) EXPECT_EQ((x),XMPP_RETURN_OK)
     41 #define TEST_BADARGUMENT(x) EXPECT_EQ((x),XMPP_RETURN_BADARGUMENT)
     42 
     43 
     44 namespace buzz {
     45 
     46 class RosterModuleTest;
     47 
     48 static void
     49 WriteString(std::ostream& os, const std::string& str) {
     50   os<<str;
     51 }
     52 
     53 static void
     54 WriteSubscriptionState(std::ostream& os, XmppSubscriptionState state)
     55 {
     56   switch (state) {
     57     case XMPP_SUBSCRIPTION_NONE:
     58       os<<"none";
     59       break;
     60     case XMPP_SUBSCRIPTION_NONE_ASKED:
     61       os<<"none_asked";
     62       break;
     63     case XMPP_SUBSCRIPTION_TO:
     64       os<<"to";
     65       break;
     66     case XMPP_SUBSCRIPTION_FROM:
     67       os<<"from";
     68       break;
     69     case XMPP_SUBSCRIPTION_FROM_ASKED:
     70       os<<"from_asked";
     71       break;
     72     case XMPP_SUBSCRIPTION_BOTH:
     73       os<<"both";
     74       break;
     75     default:
     76       os<<"unknown";
     77       break;
     78   }
     79 }
     80 
     81 static void
     82 WriteSubscriptionRequestType(std::ostream& os,
     83                              XmppSubscriptionRequestType type) {
     84   switch(type) {
     85     case XMPP_REQUEST_SUBSCRIBE:
     86       os<<"subscribe";
     87       break;
     88     case XMPP_REQUEST_UNSUBSCRIBE:
     89       os<<"unsubscribe";
     90       break;
     91     case XMPP_REQUEST_SUBSCRIBED:
     92       os<<"subscribed";
     93       break;
     94     case XMPP_REQUEST_UNSUBSCRIBED:
     95       os<<"unsubscribe";
     96       break;
     97     default:
     98       os<<"unknown";
     99       break;
    100   }
    101 }
    102 
    103 static void
    104 WritePresenceShow(std::ostream& os, XmppPresenceShow show) {
    105   switch(show) {
    106     case XMPP_PRESENCE_AWAY:
    107       os<<"away";
    108       break;
    109     case XMPP_PRESENCE_CHAT:
    110       os<<"chat";
    111       break;
    112     case XMPP_PRESENCE_DND:
    113       os<<"dnd";
    114       break;
    115     case XMPP_PRESENCE_XA:
    116       os<<"xa";
    117       break;
    118     case XMPP_PRESENCE_DEFAULT:
    119       os<<"[default]";
    120       break;
    121     default:
    122       os<<"[unknown]";
    123       break;
    124   }
    125 }
    126 
    127 static void
    128 WritePresence(std::ostream& os, const XmppPresence* presence) {
    129   if (presence == NULL) {
    130     os<<"NULL";
    131     return;
    132   }
    133 
    134   os<<"[Presence jid:";
    135   WriteString(os, presence->jid().Str());
    136   os<<" available:"<<presence->available();
    137   os<<" presence_show:";
    138   WritePresenceShow(os, presence->presence_show());
    139   os<<" priority:"<<presence->priority();
    140   os<<" status:";
    141   WriteString(os, presence->status());
    142   os<<"]"<<presence->raw_xml()->Str();
    143 }
    144 
    145 static void
    146 WriteContact(std::ostream& os, const XmppRosterContact* contact) {
    147   if (contact == NULL) {
    148     os<<"NULL";
    149     return;
    150   }
    151 
    152   os<<"[Contact jid:";
    153   WriteString(os, contact->jid().Str());
    154   os<<" name:";
    155   WriteString(os, contact->name());
    156   os<<" subscription_state:";
    157   WriteSubscriptionState(os, contact->subscription_state());
    158   os<<" groups:[";
    159   for(size_t i=0; i < contact->GetGroupCount(); ++i) {
    160     os<<(i==0?"":", ");
    161     WriteString(os, contact->GetGroup(i));
    162   }
    163   os<<"]]"<<contact->raw_xml()->Str();
    164 }
    165 
    166 //! This session handler saves all calls to a string.  These are events and
    167 //! data delivered form the engine to application code.
    168 class XmppTestRosterHandler : public XmppRosterHandler {
    169 public:
    170   XmppTestRosterHandler() {}
    171   virtual ~XmppTestRosterHandler() {}
    172 
    173   virtual void SubscriptionRequest(XmppRosterModule*,
    174                                    const Jid& requesting_jid,
    175                                    XmppSubscriptionRequestType type,
    176                                    const XmlElement* raw_xml) {
    177     ss_<<"[SubscriptionRequest Jid:" << requesting_jid.Str()<<" type:";
    178     WriteSubscriptionRequestType(ss_, type);
    179     ss_<<"]"<<raw_xml->Str();
    180   }
    181 
    182   //! Some type of presence error has occured
    183   virtual void SubscriptionError(XmppRosterModule*,
    184                                  const Jid& from,
    185                                  const XmlElement* raw_xml) {
    186     ss_<<"[SubscriptionError from:"<<from.Str()<<"]"<<raw_xml->Str();
    187   }
    188 
    189   virtual void RosterError(XmppRosterModule*,
    190                            const XmlElement* raw_xml) {
    191     ss_<<"[RosterError]"<<raw_xml->Str();
    192   }
    193 
    194   //! New presence information has come in
    195   //! The user is notified with the presence object directly.  This info is also
    196   //! added to the store accessable from the engine.
    197   virtual void IncomingPresenceChanged(XmppRosterModule*,
    198                                        const XmppPresence* presence) {
    199     ss_<<"[IncomingPresenceChanged presence:";
    200     WritePresence(ss_, presence);
    201     ss_<<"]";
    202   }
    203 
    204   //! A contact has changed
    205   //! This indicates that the data for a contact may have changed.  No
    206   //! contacts have been added or removed.
    207   virtual void ContactChanged(XmppRosterModule* roster,
    208                               const XmppRosterContact* old_contact,
    209                               size_t index) {
    210     ss_<<"[ContactChanged old_contact:";
    211     WriteContact(ss_, old_contact);
    212     ss_<<" index:"<<index<<" new_contact:";
    213     WriteContact(ss_, roster->GetRosterContact(index));
    214     ss_<<"]";
    215   }
    216 
    217   //! A set of contacts have been added
    218   //! These contacts may have been added in response to the original roster
    219   //! request or due to a "roster push" from the server.
    220   virtual void ContactsAdded(XmppRosterModule* roster,
    221                              size_t index, size_t number) {
    222     ss_<<"[ContactsAdded index:"<<index<<" number:"<<number;
    223     for (size_t i = 0; i < number; ++i) {
    224       ss_<<" "<<(index+i)<<":";
    225       WriteContact(ss_, roster->GetRosterContact(index+i));
    226     }
    227     ss_<<"]";
    228   }
    229 
    230   //! A contact has been removed
    231   //! This contact has been removed form the list.
    232   virtual void ContactRemoved(XmppRosterModule*,
    233                               const XmppRosterContact* removed_contact,
    234                               size_t index) {
    235     ss_<<"[ContactRemoved old_contact:";
    236     WriteContact(ss_, removed_contact);
    237     ss_<<" index:"<<index<<"]";
    238   }
    239 
    240   std::string Str() {
    241     return ss_.str();
    242   }
    243 
    244   std::string StrClear() {
    245     std::string result = ss_.str();
    246     ss_.str("");
    247     return result;
    248   }
    249 
    250 private:
    251   std::stringstream ss_;
    252 };
    253 
    254 //! This is the class that holds all of the unit test code for the
    255 //! roster module
    256 class RosterModuleTest : public testing::Test {
    257 public:
    258   RosterModuleTest() {}
    259   static void RunLogin(RosterModuleTest* obj, XmppEngine* engine,
    260                        XmppTestHandler* handler) {
    261     // Needs to be similar to XmppEngineTest::RunLogin
    262   }
    263 };
    264 
    265 TEST_F(RosterModuleTest, TestPresence) {
    266   XmlElement* status = new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
    267   status->AddAttr(QN_STATUS, STR_PSTN_CONFERENCE_STATUS_CONNECTING);
    268   XmlElement presence_xml(QN_PRESENCE);
    269   presence_xml.AddElement(status);
    270   talk_base::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
    271   presence->set_raw_xml(&presence_xml);
    272   EXPECT_EQ(presence->connection_status(), XMPP_CONNECTION_STATUS_CONNECTING);
    273 }
    274 
    275 TEST_F(RosterModuleTest, TestOutgoingPresence) {
    276   std::stringstream dump;
    277 
    278   talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
    279   XmppTestHandler handler(engine.get());
    280   XmppTestRosterHandler roster_handler;
    281 
    282   talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
    283   roster->set_roster_handler(&roster_handler);
    284 
    285   // Configure the roster module
    286   roster->RegisterEngine(engine.get());
    287 
    288   // Set up callbacks
    289   engine->SetOutputHandler(&handler);
    290   engine->AddStanzaHandler(&handler);
    291   engine->SetSessionHandler(&handler);
    292 
    293   // Set up minimal login info
    294   engine->SetUser(Jid("david@my-server"));
    295   // engine->SetPassword("david");
    296 
    297   // Do the whole login handshake
    298   RunLogin(this, engine.get(), &handler);
    299   EXPECT_EQ("", handler.OutputActivity());
    300 
    301   // Set some presence and broadcast it
    302   TEST_OK(roster->outgoing_presence()->
    303     set_available(XMPP_PRESENCE_AVAILABLE));
    304   TEST_OK(roster->outgoing_presence()->set_priority(-37));
    305   TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_DND));
    306   TEST_OK(roster->outgoing_presence()->
    307     set_status("I'm off to the races!<>&"));
    308   TEST_OK(roster->BroadcastPresence());
    309 
    310   EXPECT_EQ(roster_handler.StrClear(), "");
    311   EXPECT_EQ(handler.OutputActivity(),
    312     "<presence>"
    313       "<priority>-37</priority>"
    314       "<show>dnd</show>"
    315       "<status>I'm off to the races!&lt;&gt;&amp;</status>"
    316     "</presence>");
    317   EXPECT_EQ(handler.SessionActivity(), "");
    318 
    319   // Try some more
    320   TEST_OK(roster->outgoing_presence()->
    321     set_available(XMPP_PRESENCE_UNAVAILABLE));
    322   TEST_OK(roster->outgoing_presence()->set_priority(0));
    323   TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_XA));
    324   TEST_OK(roster->outgoing_presence()->set_status("Gone fishin'"));
    325   TEST_OK(roster->BroadcastPresence());
    326 
    327   EXPECT_EQ(roster_handler.StrClear(), "");
    328   EXPECT_EQ(handler.OutputActivity(),
    329     "<presence type=\"unavailable\">"
    330       "<show>xa</show>"
    331       "<status>Gone fishin'</status>"
    332     "</presence>");
    333   EXPECT_EQ(handler.SessionActivity(), "");
    334 
    335   // Okay -- we are back on
    336   TEST_OK(roster->outgoing_presence()->
    337     set_available(XMPP_PRESENCE_AVAILABLE));
    338   TEST_BADARGUMENT(roster->outgoing_presence()->set_priority(128));
    339   TEST_OK(roster->outgoing_presence()->
    340       set_presence_show(XMPP_PRESENCE_DEFAULT));
    341   TEST_OK(roster->outgoing_presence()->set_status("Cookin' wit gas"));
    342   TEST_OK(roster->BroadcastPresence());
    343 
    344   EXPECT_EQ(roster_handler.StrClear(), "");
    345   EXPECT_EQ(handler.OutputActivity(),
    346     "<presence>"
    347       "<status>Cookin' wit gas</status>"
    348     "</presence>");
    349   EXPECT_EQ(handler.SessionActivity(), "");
    350 
    351   // Set it via XML
    352   XmlElement presence_input(QN_PRESENCE);
    353   presence_input.AddAttr(QN_TYPE, "unavailable");
    354   presence_input.AddElement(new XmlElement(QN_PRIORITY));
    355   presence_input.AddText("42", 1);
    356   presence_input.AddElement(new XmlElement(QN_STATUS));
    357   presence_input.AddAttr(QN_XML_LANG, "es", 1);
    358   presence_input.AddText("Hola Amigos!", 1);
    359   presence_input.AddElement(new XmlElement(QN_STATUS));
    360   presence_input.AddText("Hey there, friend!", 1);
    361   TEST_OK(roster->outgoing_presence()->set_raw_xml(&presence_input));
    362   TEST_OK(roster->BroadcastPresence());
    363 
    364   WritePresence(dump, roster->outgoing_presence());
    365   EXPECT_EQ(dump.str(),
    366     "[Presence jid: available:0 presence_show:[default] "
    367               "priority:42 status:Hey there, friend!]"
    368     "<cli:presence type=\"unavailable\" xmlns:cli=\"jabber:client\">"
    369       "<cli:priority>42</cli:priority>"
    370       "<cli:status xml:lang=\"es\">Hola Amigos!</cli:status>"
    371       "<cli:status>Hey there, friend!</cli:status>"
    372     "</cli:presence>");
    373   dump.str("");
    374   EXPECT_EQ(roster_handler.StrClear(), "");
    375   EXPECT_EQ(handler.OutputActivity(),
    376     "<presence type=\"unavailable\">"
    377       "<priority>42</priority>"
    378       "<status xml:lang=\"es\">Hola Amigos!</status>"
    379       "<status>Hey there, friend!</status>"
    380     "</presence>");
    381   EXPECT_EQ(handler.SessionActivity(), "");
    382 
    383   // Construct a directed presence
    384   talk_base::scoped_ptr<XmppPresence> directed_presence(XmppPresence::Create());
    385   TEST_OK(directed_presence->set_available(XMPP_PRESENCE_AVAILABLE));
    386   TEST_OK(directed_presence->set_priority(120));
    387   TEST_OK(directed_presence->set_status("*very* available"));
    388   TEST_OK(roster->SendDirectedPresence(directed_presence.get(),
    389                                        Jid("myhoney (at) honey.net")));
    390 
    391   EXPECT_EQ(roster_handler.StrClear(), "");
    392   EXPECT_EQ(handler.OutputActivity(),
    393     "<presence to=\"myhoney (at) honey.net\">"
    394       "<priority>120</priority>"
    395       "<status>*very* available</status>"
    396     "</presence>");
    397   EXPECT_EQ(handler.SessionActivity(), "");
    398 }
    399 
    400 TEST_F(RosterModuleTest, TestIncomingPresence) {
    401   talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
    402   XmppTestHandler handler(engine.get());
    403   XmppTestRosterHandler roster_handler;
    404 
    405   talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
    406   roster->set_roster_handler(&roster_handler);
    407 
    408   // Configure the roster module
    409   roster->RegisterEngine(engine.get());
    410 
    411   // Set up callbacks
    412   engine->SetOutputHandler(&handler);
    413   engine->AddStanzaHandler(&handler);
    414   engine->SetSessionHandler(&handler);
    415 
    416   // Set up minimal login info
    417   engine->SetUser(Jid("david@my-server"));
    418   // engine->SetPassword("david");
    419 
    420   // Do the whole login handshake
    421   RunLogin(this, engine.get(), &handler);
    422   EXPECT_EQ("", handler.OutputActivity());
    423 
    424   // Load up with a bunch of data
    425   std::string input;
    426   input = "<presence from='maude (at) example.net/studio' "
    427                     "to='david@my-server/test'/>"
    428           "<presence from='walter (at) example.net/home' "
    429                     "to='david@my-server/test'>"
    430             "<priority>-10</priority>"
    431             "<show>xa</show>"
    432             "<status>Off bowling</status>"
    433           "</presence>"
    434           "<presence from='walter (at) example.net/alley' "
    435                     "to='david@my-server/test'>"
    436             "<priority>20</priority>"
    437             "<status>Looking for toes...</status>"
    438           "</presence>"
    439           "<presence from='donny (at) example.net/alley' "
    440                     "to='david@my-server/test'>"
    441             "<priority>10</priority>"
    442             "<status>Throwing rocks</status>"
    443           "</presence>";
    444   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    445 
    446   EXPECT_EQ(roster_handler.StrClear(),
    447     "[IncomingPresenceChanged "
    448       "presence:[Presence jid:maude (at) example.net/studio available:1 "
    449                  "presence_show:[default] priority:0 status:]"
    450         "<cli:presence from=\"maude (at) example.net/studio\" "
    451                       "to=\"david@my-server/test\" "
    452                       "xmlns:cli=\"jabber:client\"/>]"
    453     "[IncomingPresenceChanged "
    454       "presence:[Presence jid:walter (at) example.net/home available:1 "
    455                  "presence_show:xa priority:-10 status:Off bowling]"
    456         "<cli:presence from=\"walter (at) example.net/home\" "
    457                       "to=\"david@my-server/test\" "
    458                       "xmlns:cli=\"jabber:client\">"
    459           "<cli:priority>-10</cli:priority>"
    460           "<cli:show>xa</cli:show>"
    461           "<cli:status>Off bowling</cli:status>"
    462         "</cli:presence>]"
    463     "[IncomingPresenceChanged "
    464       "presence:[Presence jid:walter (at) example.net/alley available:1 "
    465                  "presence_show:[default] "
    466                  "priority:20 status:Looking for toes...]"
    467         "<cli:presence from=\"walter (at) example.net/alley\" "
    468                        "to=\"david@my-server/test\" "
    469                        "xmlns:cli=\"jabber:client\">"
    470           "<cli:priority>20</cli:priority>"
    471           "<cli:status>Looking for toes...</cli:status>"
    472         "</cli:presence>]"
    473     "[IncomingPresenceChanged "
    474       "presence:[Presence jid:donny (at) example.net/alley available:1 "
    475                  "presence_show:[default] priority:10 status:Throwing rocks]"
    476         "<cli:presence from=\"donny (at) example.net/alley\" "
    477                       "to=\"david@my-server/test\" "
    478                       "xmlns:cli=\"jabber:client\">"
    479           "<cli:priority>10</cli:priority>"
    480           "<cli:status>Throwing rocks</cli:status>"
    481         "</cli:presence>]");
    482   EXPECT_EQ(handler.OutputActivity(), "");
    483   handler.SessionActivity(); // Ignore the session output
    484 
    485   // Now look at the data structure we've built
    486   EXPECT_EQ(roster->GetIncomingPresenceCount(), static_cast<size_t>(4));
    487   EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("maude (at) example.net")),
    488             static_cast<size_t>(1));
    489   EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("walter (at) example.net")),
    490             static_cast<size_t>(2));
    491 
    492   const XmppPresence * presence;
    493   presence = roster->GetIncomingPresenceForJid(Jid("walter (at) example.net"), 1);
    494 
    495   std::stringstream dump;
    496   WritePresence(dump, presence);
    497   EXPECT_EQ(dump.str(),
    498           "[Presence jid:walter (at) example.net/alley available:1 "
    499             "presence_show:[default] priority:20 status:Looking for toes...]"
    500               "<cli:presence from=\"walter (at) example.net/alley\" "
    501                             "to=\"david@my-server/test\" "
    502                             "xmlns:cli=\"jabber:client\">"
    503                 "<cli:priority>20</cli:priority>"
    504                 "<cli:status>Looking for toes...</cli:status>"
    505               "</cli:presence>");
    506   dump.str("");
    507 
    508   // Maude took off...
    509   input = "<presence from='maude (at) example.net/studio' "
    510                     "to='david@my-server/test' "
    511                     "type='unavailable'>"
    512             "<status>Stealing my rug back</status>"
    513             "<priority>-10</priority>"
    514           "</presence>";
    515   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    516 
    517   EXPECT_EQ(roster_handler.StrClear(),
    518     "[IncomingPresenceChanged "
    519       "presence:[Presence jid:maude (at) example.net/studio available:0 "
    520                  "presence_show:[default] priority:-10 "
    521                  "status:Stealing my rug back]"
    522         "<cli:presence from=\"maude (at) example.net/studio\" "
    523                       "to=\"david@my-server/test\" type=\"unavailable\" "
    524                       "xmlns:cli=\"jabber:client\">"
    525           "<cli:status>Stealing my rug back</cli:status>"
    526           "<cli:priority>-10</cli:priority>"
    527         "</cli:presence>]");
    528   EXPECT_EQ(handler.OutputActivity(), "");
    529   handler.SessionActivity(); // Ignore the session output
    530 }
    531 
    532 TEST_F(RosterModuleTest, TestPresenceSubscription) {
    533   talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
    534   XmppTestHandler handler(engine.get());
    535   XmppTestRosterHandler roster_handler;
    536 
    537   talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
    538   roster->set_roster_handler(&roster_handler);
    539 
    540   // Configure the roster module
    541   roster->RegisterEngine(engine.get());
    542 
    543   // Set up callbacks
    544   engine->SetOutputHandler(&handler);
    545   engine->AddStanzaHandler(&handler);
    546   engine->SetSessionHandler(&handler);
    547 
    548   // Set up minimal login info
    549   engine->SetUser(Jid("david@my-server"));
    550   // engine->SetPassword("david");
    551 
    552   // Do the whole login handshake
    553   RunLogin(this, engine.get(), &handler);
    554   EXPECT_EQ("", handler.OutputActivity());
    555 
    556   // Test incoming requests
    557   std::string input;
    558   input =
    559     "<presence from='maude (at) example.net' type='subscribe'/>"
    560     "<presence from='maude (at) example.net' type='unsubscribe'/>"
    561     "<presence from='maude (at) example.net' type='subscribed'/>"
    562     "<presence from='maude (at) example.net' type='unsubscribed'/>";
    563   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    564 
    565   EXPECT_EQ(roster_handler.StrClear(),
    566     "[SubscriptionRequest Jid:maude (at) example.net type:subscribe]"
    567       "<cli:presence from=\"maude (at) example.net\" type=\"subscribe\" "
    568                     "xmlns:cli=\"jabber:client\"/>"
    569     "[SubscriptionRequest Jid:maude (at) example.net type:unsubscribe]"
    570       "<cli:presence from=\"maude (at) example.net\" type=\"unsubscribe\" "
    571                     "xmlns:cli=\"jabber:client\"/>"
    572     "[SubscriptionRequest Jid:maude (at) example.net type:subscribed]"
    573       "<cli:presence from=\"maude (at) example.net\" type=\"subscribed\" "
    574                     "xmlns:cli=\"jabber:client\"/>"
    575     "[SubscriptionRequest Jid:maude (at) example.net type:unsubscribe]"
    576       "<cli:presence from=\"maude (at) example.net\" type=\"unsubscribed\" "
    577                     "xmlns:cli=\"jabber:client\"/>");
    578   EXPECT_EQ(handler.OutputActivity(), "");
    579   handler.SessionActivity(); // Ignore the session output
    580 
    581   TEST_OK(roster->RequestSubscription(Jid("maude (at) example.net")));
    582   TEST_OK(roster->CancelSubscription(Jid("maude (at) example.net")));
    583   TEST_OK(roster->ApproveSubscriber(Jid("maude (at) example.net")));
    584   TEST_OK(roster->CancelSubscriber(Jid("maude (at) example.net")));
    585 
    586   EXPECT_EQ(roster_handler.StrClear(), "");
    587   EXPECT_EQ(handler.OutputActivity(),
    588     "<presence to=\"maude (at) example.net\" type=\"subscribe\"/>"
    589     "<presence to=\"maude (at) example.net\" type=\"unsubscribe\"/>"
    590     "<presence to=\"maude (at) example.net\" type=\"subscribed\"/>"
    591     "<presence to=\"maude (at) example.net\" type=\"unsubscribed\"/>");
    592   EXPECT_EQ(handler.SessionActivity(), "");
    593 }
    594 
    595 TEST_F(RosterModuleTest, TestRosterReceive) {
    596   talk_base::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
    597   XmppTestHandler handler(engine.get());
    598   XmppTestRosterHandler roster_handler;
    599 
    600   talk_base::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
    601   roster->set_roster_handler(&roster_handler);
    602 
    603   // Configure the roster module
    604   roster->RegisterEngine(engine.get());
    605 
    606   // Set up callbacks
    607   engine->SetOutputHandler(&handler);
    608   engine->AddStanzaHandler(&handler);
    609   engine->SetSessionHandler(&handler);
    610 
    611   // Set up minimal login info
    612   engine->SetUser(Jid("david@my-server"));
    613   // engine->SetPassword("david");
    614 
    615   // Do the whole login handshake
    616   RunLogin(this, engine.get(), &handler);
    617   EXPECT_EQ("", handler.OutputActivity());
    618 
    619   // Request a roster update
    620   TEST_OK(roster->RequestRosterUpdate());
    621 
    622   EXPECT_EQ(roster_handler.StrClear(),"");
    623   EXPECT_EQ(handler.OutputActivity(),
    624     "<iq type=\"get\" id=\"2\">"
    625       "<query xmlns=\"jabber:iq:roster\"/>"
    626     "</iq>");
    627   EXPECT_EQ(handler.SessionActivity(), "");
    628 
    629   // Prime the roster with a starting set
    630   std::string input =
    631     "<iq to='david@myserver/test' type='result' id='2'>"
    632       "<query xmlns='jabber:iq:roster'>"
    633         "<item jid='maude (at) example.net' "
    634               "name='Maude Lebowski' "
    635               "subscription='none' "
    636               "ask='subscribe'>"
    637           "<group>Business Partners</group>"
    638         "</item>"
    639         "<item jid='walter (at) example.net' "
    640               "name='Walter Sobchak' "
    641               "subscription='both'>"
    642           "<group>Friends</group>"
    643           "<group>Bowling Team</group>"
    644           "<group>Bowling League</group>"
    645         "</item>"
    646         "<item jid='donny (at) example.net' "
    647               "name='Donny' "
    648               "subscription='both'>"
    649           "<group>Friends</group>"
    650           "<group>Bowling Team</group>"
    651           "<group>Bowling League</group>"
    652         "</item>"
    653         "<item jid='jeffrey (at) example.net' "
    654               "name='The Big Lebowski' "
    655               "subscription='to'>"
    656           "<group>Business Partners</group>"
    657         "</item>"
    658         "<item jid='jesus (at) example.net' "
    659               "name='Jesus Quintana' "
    660               "subscription='from'>"
    661           "<group>Bowling League</group>"
    662         "</item>"
    663       "</query>"
    664     "</iq>";
    665 
    666   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    667 
    668   EXPECT_EQ(roster_handler.StrClear(),
    669     "[ContactsAdded index:0 number:5 "
    670       "0:[Contact jid:maude (at) example.net name:Maude Lebowski "
    671                  "subscription_state:none_asked "
    672                  "groups:[Business Partners]]"
    673         "<ros:item jid=\"maude (at) example.net\" name=\"Maude Lebowski\" "
    674                   "subscription=\"none\" ask=\"subscribe\" "
    675                   "xmlns:ros=\"jabber:iq:roster\">"
    676           "<ros:group>Business Partners</ros:group>"
    677         "</ros:item> "
    678       "1:[Contact jid:walter (at) example.net name:Walter Sobchak "
    679                  "subscription_state:both "
    680                  "groups:[Friends, Bowling Team, Bowling League]]"
    681         "<ros:item jid=\"walter (at) example.net\" name=\"Walter Sobchak\" "
    682                   "subscription=\"both\" "
    683                   "xmlns:ros=\"jabber:iq:roster\">"
    684           "<ros:group>Friends</ros:group>"
    685           "<ros:group>Bowling Team</ros:group>"
    686           "<ros:group>Bowling League</ros:group>"
    687         "</ros:item> "
    688       "2:[Contact jid:donny (at) example.net name:Donny "
    689                  "subscription_state:both "
    690                  "groups:[Friends, Bowling Team, Bowling League]]"
    691         "<ros:item jid=\"donny (at) example.net\" name=\"Donny\" "
    692                   "subscription=\"both\" "
    693                   "xmlns:ros=\"jabber:iq:roster\">"
    694           "<ros:group>Friends</ros:group>"
    695           "<ros:group>Bowling Team</ros:group>"
    696           "<ros:group>Bowling League</ros:group>"
    697         "</ros:item> "
    698       "3:[Contact jid:jeffrey (at) example.net name:The Big Lebowski "
    699                  "subscription_state:to "
    700                  "groups:[Business Partners]]"
    701         "<ros:item jid=\"jeffrey (at) example.net\" name=\"The Big Lebowski\" "
    702                   "subscription=\"to\" "
    703                   "xmlns:ros=\"jabber:iq:roster\">"
    704           "<ros:group>Business Partners</ros:group>"
    705         "</ros:item> "
    706       "4:[Contact jid:jesus (at) example.net name:Jesus Quintana "
    707                  "subscription_state:from groups:[Bowling League]]"
    708         "<ros:item jid=\"jesus (at) example.net\" name=\"Jesus Quintana\" "
    709                   "subscription=\"from\" xmlns:ros=\"jabber:iq:roster\">"
    710           "<ros:group>Bowling League</ros:group>"
    711         "</ros:item>]");
    712   EXPECT_EQ(handler.OutputActivity(), "");
    713   EXPECT_EQ(handler.SessionActivity(), "");
    714 
    715   // Request that someone be added
    716   talk_base::scoped_ptr<XmppRosterContact> contact(XmppRosterContact::Create());
    717   TEST_OK(contact->set_jid(Jid("brandt (at) example.net")));
    718   TEST_OK(contact->set_name("Brandt"));
    719   TEST_OK(contact->AddGroup("Business Partners"));
    720   TEST_OK(contact->AddGroup("Watchers"));
    721   TEST_OK(contact->AddGroup("Friends"));
    722   TEST_OK(contact->RemoveGroup("Friends")); // Maybe not...
    723   TEST_OK(roster->RequestRosterChange(contact.get()));
    724 
    725   EXPECT_EQ(roster_handler.StrClear(), "");
    726   EXPECT_EQ(handler.OutputActivity(),
    727     "<iq type=\"set\" id=\"3\">"
    728       "<query xmlns=\"jabber:iq:roster\">"
    729         "<item jid=\"brandt (at) example.net\" "
    730               "name=\"Brandt\">"
    731           "<group>Business Partners</group>"
    732           "<group>Watchers</group>"
    733         "</item>"
    734       "</query>"
    735     "</iq>");
    736   EXPECT_EQ(handler.SessionActivity(), "");
    737 
    738   // Get the push from the server
    739   input =
    740     "<iq type='result' to='david@my-server/test' id='3'/>"
    741     "<iq type='set' id='server_1'>"
    742       "<query xmlns='jabber:iq:roster'>"
    743         "<item jid='brandt (at) example.net' "
    744               "name='Brandt' "
    745               "subscription='none'>"
    746           "<group>Business Partners</group>"
    747           "<group>Watchers</group>"
    748         "</item>"
    749       "</query>"
    750     "</iq>";
    751   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    752 
    753   EXPECT_EQ(roster_handler.StrClear(),
    754     "[ContactsAdded index:5 number:1 "
    755       "5:[Contact jid:brandt (at) example.net name:Brandt "
    756                  "subscription_state:none "
    757                  "groups:[Business Partners, Watchers]]"
    758         "<ros:item jid=\"brandt (at) example.net\" name=\"Brandt\" "
    759                   "subscription=\"none\" "
    760                   "xmlns:ros=\"jabber:iq:roster\">"
    761           "<ros:group>Business Partners</ros:group>"
    762           "<ros:group>Watchers</ros:group>"
    763         "</ros:item>]");
    764   EXPECT_EQ(handler.OutputActivity(),
    765     "<iq type=\"result\" id=\"server_1\"/>");
    766   EXPECT_EQ(handler.SessionActivity(), "");
    767 
    768   // Get a contact update
    769   input =
    770     "<iq type='set' id='server_2'>"
    771       "<query xmlns='jabber:iq:roster'>"
    772         "<item jid='walter (at) example.net' "
    773               "name='Walter Sobchak' "
    774               "subscription='both'>"
    775           "<group>Friends</group>"
    776           "<group>Bowling Team</group>"
    777           "<group>Bowling League</group>"
    778           "<group>Not wrong, just an...</group>"
    779         "</item>"
    780       "</query>"
    781     "</iq>";
    782 
    783   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    784 
    785   EXPECT_EQ(roster_handler.StrClear(),
    786     "[ContactChanged "
    787       "old_contact:[Contact jid:walter (at) example.net name:Walter Sobchak "
    788                            "subscription_state:both "
    789                            "groups:[Friends, Bowling Team, Bowling League]]"
    790         "<ros:item jid=\"walter (at) example.net\" name=\"Walter Sobchak\" "
    791                   "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
    792           "<ros:group>Friends</ros:group>"
    793           "<ros:group>Bowling Team</ros:group>"
    794           "<ros:group>Bowling League</ros:group>"
    795           "</ros:item> "
    796       "index:1 "
    797       "new_contact:[Contact jid:walter (at) example.net name:Walter Sobchak "
    798                            "subscription_state:both "
    799                            "groups:[Friends, Bowling Team, Bowling League, "
    800                                    "Not wrong, just an...]]"
    801         "<ros:item jid=\"walter (at) example.net\" name=\"Walter Sobchak\" "
    802                   "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
    803           "<ros:group>Friends</ros:group>"
    804           "<ros:group>Bowling Team</ros:group>"
    805           "<ros:group>Bowling League</ros:group>"
    806           "<ros:group>Not wrong, just an...</ros:group>"
    807         "</ros:item>]");
    808   EXPECT_EQ(handler.OutputActivity(),
    809     "<iq type=\"result\" id=\"server_2\"/>");
    810   EXPECT_EQ(handler.SessionActivity(), "");
    811 
    812   // Remove a contact
    813   TEST_OK(roster->RequestRosterRemove(Jid("jesus (at) example.net")));
    814 
    815   EXPECT_EQ(roster_handler.StrClear(), "");
    816   EXPECT_EQ(handler.OutputActivity(),
    817     "<iq type=\"set\" id=\"4\">"
    818       "<query xmlns=\"jabber:iq:roster\" jid=\"jesus (at) example.net\" "
    819              "subscription=\"remove\"/>"
    820     "</iq>");
    821   EXPECT_EQ(handler.SessionActivity(), "");
    822 
    823   // Response from the server
    824   input =
    825     "<iq type='result' to='david@my-server/test' id='4'/>"
    826     "<iq type='set' id='server_3'>"
    827       "<query xmlns='jabber:iq:roster'>"
    828         "<item jid='jesus (at) example.net' "
    829               "subscription='remove'>"
    830         "</item>"
    831       "</query>"
    832     "</iq>";
    833   TEST_OK(engine->HandleInput(input.c_str(), input.length()));
    834 
    835   EXPECT_EQ(roster_handler.StrClear(),
    836     "[ContactRemoved "
    837       "old_contact:[Contact jid:jesus (at) example.net name:Jesus Quintana "
    838                            "subscription_state:from groups:[Bowling League]]"
    839         "<ros:item jid=\"jesus (at) example.net\" name=\"Jesus Quintana\" "
    840                   "subscription=\"from\" "
    841                   "xmlns:ros=\"jabber:iq:roster\">"
    842           "<ros:group>Bowling League</ros:group>"
    843         "</ros:item> index:4]");
    844   EXPECT_EQ(handler.OutputActivity(),
    845     "<iq type=\"result\" id=\"server_3\"/>");
    846   EXPECT_EQ(handler.SessionActivity(), "");
    847 }
    848 
    849 }
    850