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