diff options
Diffstat (limited to 'Swiften')
-rw-r--r-- | Swiften/JID/JID.h | 8 | ||||
-rw-r--r-- | Swiften/MUC/MUC.h | 4 | ||||
-rw-r--r-- | Swiften/MUC/MUCImpl.cpp | 41 | ||||
-rw-r--r-- | Swiften/MUC/MUCImpl.h | 6 | ||||
-rw-r--r-- | Swiften/MUC/UnitTest/MUCTest.cpp | 74 | ||||
-rw-r--r-- | Swiften/MUC/UnitTest/MockMUC.h | 1 |
6 files changed, 122 insertions, 12 deletions
diff --git a/Swiften/JID/JID.h b/Swiften/JID/JID.h index 126a7b1..aefd3df 100644 --- a/Swiften/JID/JID.h +++ b/Swiften/JID/JID.h @@ -102,70 +102,78 @@ namespace Swift { */ const std::string& getResource() const { return resource_; } /** * Is a bare JID, i.e. has no resource part. */ bool isBare() const { return !hasResource_; } /** * Returns the given node, escaped according to XEP-0106. * The resulting node is a valid node for a JID, whereas the input value can contain characters * that are not allowed. */ static std::string getEscapedNode(const std::string& node); /** * Returns the node of the current JID, unescaped according to XEP-0106. */ std::string getUnescapedNode() const; /** * Get the JID without a resource. * @return Invalid if the original is invalid. */ JID toBare() const { JID result(*this); result.hasResource_ = false; result.resource_ = ""; return result; } + /** + * Get the full JID with the supplied resource. + */ + JID withResource(const std::string& resource) const { + JID result(this->getNode(), this->getDomain(), resource); + return result; + } + std::string toString() const; bool equals(const JID& o, CompareType compareType) const { return compare(o, compareType) == 0; } int compare(const JID& o, CompareType compareType) const; operator std::string() const { return toString(); } bool operator<(const Swift::JID& b) const { return compare(b, Swift::JID::WithResource) < 0; } SWIFTEN_API friend std::ostream& operator<<(std::ostream& os, const Swift::JID& j); friend bool operator==(const Swift::JID& a, const Swift::JID& b) { return a.compare(b, Swift::JID::WithResource) == 0; } friend bool operator!=(const Swift::JID& a, const Swift::JID& b) { return a.compare(b, Swift::JID::WithResource) != 0; } /** * Returns an empty optional if the JID is invalid, and an * optional with a value if the JID is valid. */ static boost::optional<JID> parse(const std::string&); /** * If Swiften was compiled with SWIFTEN_JID_NO_DEFAULT_IDN_CONVERTER (not default), use this method at * the beginning of the program to set an IDN converter to use for JID IDN conversions. diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h index 0dcccd9..8815489 100644 --- a/Swiften/MUC/MUC.h +++ b/Swiften/MUC/MUC.h @@ -1,98 +1,100 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once #include <Swiften/JID/JID.h> #include <Swiften/Base/API.h> #include <string> #include <Swiften/Elements/Message.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Elements/MUCOccupant.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Elements/MUCOwnerPayload.h> #include <Swiften/Elements/MUCAdminPayload.h> #include <Swiften/Elements/Form.h> #include <boost/shared_ptr.hpp> #include <Swiften/Base/boost_bsignals.h> #include <boost/signals/connection.hpp> #include <map> namespace Swift { class StanzaChannel; class IQRouter; class DirectedPresenceSender; class SWIFTEN_API MUC { public: typedef boost::shared_ptr<MUC> ref; enum JoinResult { JoinSucceeded, JoinFailed }; enum LeavingType { LeavePart, LeaveKick, LeaveBan, LeaveDestroy, LeaveNotMember, Disconnect }; public: virtual ~MUC(); /** * Returns the (bare) JID of the MUC. */ virtual JID getJID() const = 0; /** * Returns if the room is unlocked and other people can join the room. * @return True if joinable by others; false otherwise. */ virtual bool isUnlocked() const = 0; virtual void joinAs(const std::string &nick) = 0; virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) = 0; /*virtual void queryRoomInfo(); */ /*virtual void queryRoomItems(); */ /*virtual std::string getCurrentNick() = 0; */ virtual std::map<std::string, MUCOccupant> getOccupants() const = 0; + virtual void changeNickname(const std::string& newNickname) = 0; virtual void part() = 0; /*virtual void handleIncomingMessage(Message::ref message) = 0; */ /** Expose public so it can be called when e.g. user goes offline */ virtual void handleUserLeft(LeavingType) = 0; /** Get occupant information*/ virtual const MUCOccupant& getOccupant(const std::string& nick) = 0; virtual bool hasOccupant(const std::string& nick) = 0; virtual void kickOccupant(const JID& jid) = 0; virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role) = 0; virtual void requestAffiliationList(MUCOccupant::Affiliation) = 0; virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) = 0; virtual void changeSubject(const std::string& subject) = 0; virtual void requestConfigurationForm() = 0; virtual void configureRoom(Form::ref) = 0; virtual void cancelConfigureRoom() = 0; virtual void destroyRoom() = 0; /** Send an invite for the person to join the MUC */ virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false) = 0; virtual void setCreateAsReservedIfNew() = 0; virtual void setPassword(const boost::optional<std::string>& password) = 0; public: boost::signal<void (const std::string& /*nick*/)> onJoinComplete; boost::signal<void (ErrorPayload::ref)> onJoinFailed; boost::signal<void (ErrorPayload::ref, const JID&, MUCOccupant::Role)> onRoleChangeFailed; boost::signal<void (ErrorPayload::ref, const JID&, MUCOccupant::Affiliation)> onAffiliationChangeFailed; boost::signal<void (ErrorPayload::ref)> onConfigurationFailed; boost::signal<void (ErrorPayload::ref)> onAffiliationListFailed; boost::signal<void (Presence::ref)> onOccupantPresenceChange; boost::signal<void (const std::string&, const MUCOccupant& /*now*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged; boost::signal<void (const std::string&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged; boost::signal<void (const MUCOccupant&)> onOccupantJoined; + boost::signal<void (const std::string& /*oldNickname*/, const std::string& /*newNickname*/ )> onOccupantNicknameChanged; boost::signal<void (const MUCOccupant&, LeavingType, const std::string& /*reason*/)> onOccupantLeft; boost::signal<void (Form::ref)> onConfigurationFormReceived; boost::signal<void (MUCOccupant::Affiliation, const std::vector<JID>&)> onAffiliationListReceived; boost::signal<void ()> onUnlocked; /* boost::signal<void (const MUCInfo&)> onInfoResult; */ /* boost::signal<void (const blah&)> onItemsResult; */ }; } diff --git a/Swiften/MUC/MUCImpl.cpp b/Swiften/MUC/MUCImpl.cpp index a1854e3..ab5faf4 100644 --- a/Swiften/MUC/MUCImpl.cpp +++ b/Swiften/MUC/MUCImpl.cpp @@ -1,37 +1,37 @@ /* - * Copyright (c) 2010-2013 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swiften/MUC/MUCImpl.h> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Base/foreach.h> #include <Swiften/Presence/DirectedPresenceSender.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Elements/Form.h> #include <Swiften/Elements/Message.h> #include <Swiften/Elements/IQ.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/Elements/MUCAdminPayload.h> #include <Swiften/Elements/MUCPayload.h> #include <Swiften/Elements/MUCDestroyPayload.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Queries/GenericRequest.h> namespace Swift { typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; MUCImpl::MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) { scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUCImpl::handleIncomingPresence, this, _1)); } MUCImpl::~MUCImpl() { @@ -58,162 +58,185 @@ void MUCImpl::setPassword(const boost::optional<std::string>& newPassword) { * Join the MUC with context since date. */ void MUCImpl::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) { joinSince_ = since; internalJoin(nick); } std::map<std::string, MUCOccupant> MUCImpl::getOccupants() const { return occupants; } void MUCImpl::internalJoin(const std::string &nick) { //TODO: history request joinComplete_ = false; joinSucceeded_ = false; mucRegistry->addMUC(getJID()); ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); Presence::ref joinPresence = boost::make_shared<Presence>(*presenceSender->getLastSentUndirectedPresence()); assert(joinPresence->getType() == Presence::Available); joinPresence->setTo(ownMUCJID); MUCPayload::ref mucPayload = boost::make_shared<MUCPayload>(); if (joinSince_ != boost::posix_time::not_a_date_time) { mucPayload->setSince(joinSince_); } if (password) { mucPayload->setPassword(*password); } joinPresence->addPayload(mucPayload); presenceSender->sendPresence(joinPresence); } +void MUCImpl::changeNickname(const std::string& newNickname) { + Presence::ref changeNicknamePresence = boost::make_shared<Presence>(); + changeNicknamePresence->setTo(ownMUCJID.toBare().toString() + std::string("/") + newNickname); + presenceSender->sendPresence(changeNicknamePresence); +} + void MUCImpl::part() { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); mucRegistry->removeMUC(getJID()); } void MUCImpl::handleUserLeft(LeavingType type) { std::map<std::string,MUCOccupant>::iterator i = occupants.find(ownMUCJID.getResource()); if (i != occupants.end()) { MUCOccupant me = i->second; occupants.erase(i); onOccupantLeft(me, type, ""); } occupants.clear(); joinComplete_ = false; joinSucceeded_ = false; isUnlocked_ = false; presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); } void MUCImpl::handleIncomingPresence(Presence::ref presence) { if (!isFromMUC(presence->getFrom())) { return; } MUCUserPayload::ref mucPayload; foreach (MUCUserPayload::ref payload, presence->getPayloads<MUCUserPayload>()) { if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) { mucPayload = payload; } } // On the first incoming presence, check if our join has succeeded // (i.e. we start getting non-error presence from the MUC) or not if (!joinSucceeded_) { if (presence->getType() == Presence::Error) { std::string reason; onJoinFailed(presence->getPayload<ErrorPayload>()); return; } else { joinSucceeded_ = true; presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); } } std::string nick = presence->getFrom().getResource(); if (nick.empty()) { return; } MUCOccupant::Role role(MUCOccupant::NoRole); MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); boost::optional<JID> realJID; if (mucPayload && mucPayload->getItems().size() > 0) { role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole; affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation; realJID = mucPayload->getItems()[0].realJID; } //100 is non-anonymous //TODO: 100 may also be specified in a <message/> //170 is room logging to http //TODO: Nick changes if (presence->getType() == Presence::Unavailable) { LeavingType type = LeavePart; + boost::optional<std::string> newNickname; if (mucPayload) { if (boost::dynamic_pointer_cast<MUCDestroyPayload>(mucPayload->getPayload())) { type = LeaveDestroy; } else foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) { if (status.code == 307) { type = LeaveKick; } else if (status.code == 301) { type = LeaveBan; } else if (status.code == 321) { type = LeaveNotMember; } + else if (status.code == 303) { + if (mucPayload->getItems().size() == 1) { + newNickname = mucPayload->getItems()[0].nick; + } + } } } - - if (presence->getFrom() == ownMUCJID) { - handleUserLeft(type); - return; - } - else { + if (newNickname) { std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick); if (i != occupants.end()) { - //TODO: part type MUCOccupant occupant = i->second; occupants.erase(i); - onOccupantLeft(occupant, type, ""); + occupant.setNick(newNickname.get()); + occupants.insert(std::make_pair(newNickname.get(), occupant)); + onOccupantNicknameChanged(nick, newNickname.get()); + } + } + else { + if (presence->getFrom() == ownMUCJID) { + handleUserLeft(type); + return; + } + else { + std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick); + if (i != occupants.end()) { + //TODO: part type + MUCOccupant occupant = i->second; + occupants.erase(i); + onOccupantLeft(occupant, type, ""); + } } } } else if (presence->getType() == Presence::Available) { std::map<std::string, MUCOccupant>::iterator it = occupants.find(nick); MUCOccupant occupant(nick, role, affiliation); bool isJoin = true; if (realJID) { occupant.setRealJID(realJID.get()); } if (it != occupants.end()) { isJoin = false; MUCOccupant oldOccupant = it->second; if (oldOccupant.getRole() != role) { onOccupantRoleChanged(nick, occupant, oldOccupant.getRole()); } if (oldOccupant.getAffiliation() != affiliation) { onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation()); } occupants.erase(it); } std::pair<std::map<std::string, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant)); if (isJoin) { onOccupantJoined(result.first->second); } onOccupantPresenceChange(presence); } if (mucPayload && !joinComplete_) { bool isLocked = false; foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) { if (status.code == 110) { /* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */ joinComplete_ = true; if (ownMUCJID != presence->getFrom()) { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); diff --git a/Swiften/MUC/MUCImpl.h b/Swiften/MUC/MUCImpl.h index 846ddcf..8eabb80 100644 --- a/Swiften/MUC/MUCImpl.h +++ b/Swiften/MUC/MUCImpl.h @@ -26,70 +26,76 @@ namespace Swift { class StanzaChannel; class IQRouter; class DirectedPresenceSender; class SWIFTEN_API MUCImpl : public MUC { public: typedef boost::shared_ptr<MUCImpl> ref; public: MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry); virtual ~MUCImpl(); /** * Returns the (bare) JID of the MUC. */ virtual JID getJID() const { return ownMUCJID.toBare(); } /** * Returns if the room is unlocked and other people can join the room. * @return True if joinable by others; false otherwise. */ virtual bool isUnlocked() const { return isUnlocked_; } virtual void joinAs(const std::string &nick); virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since); /*virtual void queryRoomInfo(); */ /*virtual void queryRoomItems(); */ /*virtual std::string getCurrentNick(); */ virtual std::map<std::string, MUCOccupant> getOccupants() const; + + /** + * Send a new presence to the MUC indicating a nickname change. Any custom status the user had in the is cleared. + * @param newNickname The nickname to change to. + */ + virtual void changeNickname(const std::string& newNickname); virtual void part(); /*virtual void handleIncomingMessage(Message::ref message); */ /** Expose public so it can be called when e.g. user goes offline */ virtual void handleUserLeft(LeavingType); /** Get occupant information*/ virtual const MUCOccupant& getOccupant(const std::string& nick); virtual bool hasOccupant(const std::string& nick); virtual void kickOccupant(const JID& jid); virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role); virtual void requestAffiliationList(MUCOccupant::Affiliation); virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation); virtual void changeSubject(const std::string& subject); virtual void requestConfigurationForm(); virtual void configureRoom(Form::ref); virtual void cancelConfigureRoom(); virtual void destroyRoom(); /** Send an invite for the person to join the MUC */ virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false); virtual void setCreateAsReservedIfNew() {createAsReservedIfNew = true;} virtual void setPassword(const boost::optional<std::string>& password); private: bool isFromMUC(const JID& j) const { return ownMUCJID.equals(j, JID::WithoutResource); } const std::string& getOwnNick() const { return ownMUCJID.getResource(); } private: void handleIncomingPresence(Presence::ref presence); void internalJoin(const std::string& nick); void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref); void handleOccupantRoleChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Role); diff --git a/Swiften/MUC/UnitTest/MUCTest.cpp b/Swiften/MUC/UnitTest/MUCTest.cpp index d1a21b0..773edb6 100644 --- a/Swiften/MUC/UnitTest/MUCTest.cpp +++ b/Swiften/MUC/UnitTest/MUCTest.cpp @@ -1,78 +1,80 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <boost/bind.hpp> #include <Swiften/MUC/MUCImpl.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Presence/StanzaChannelPresenceSender.h> #include <Swiften/Presence/DirectedPresenceSender.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/Elements/MUCOwnerPayload.h> #include <Swiften/Elements/VCard.h> #include <Swiften/Elements/CapsInfo.h> using namespace Swift; class MUCTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(MUCTest); CPPUNIT_TEST(testJoin); CPPUNIT_TEST(testJoin_ChangePresenceDuringJoinDoesNotSendPresenceBeforeJoinSuccess); CPPUNIT_TEST(testJoin_ChangePresenceDuringJoinResendsPresenceAfterJoinSuccess); CPPUNIT_TEST(testCreateInstant); CPPUNIT_TEST(testReplicateBug); + CPPUNIT_TEST(testNicknameChange); /*CPPUNIT_TEST(testJoin_Success); CPPUNIT_TEST(testJoin_Fail);*/ CPPUNIT_TEST_SUITE_END(); public: void setUp() { channel = new DummyStanzaChannel(); router = new IQRouter(channel); mucRegistry = new MUCRegistry(); stanzaChannelPresenceSender = new StanzaChannelPresenceSender(channel); presenceSender = new DirectedPresenceSender(stanzaChannelPresenceSender); + nickChanges = 0; } void tearDown() { delete presenceSender; delete stanzaChannelPresenceSender; delete mucRegistry; delete router; delete channel; } void testJoin() { MUC::ref testling = createMUC(JID("foo@bar.com")); testling->joinAs("Alice"); CPPUNIT_ASSERT(mucRegistry->isMUC(JID("foo@bar.com"))); Presence::ref p = channel->getStanzaAtIndex<Presence>(0); CPPUNIT_ASSERT(p); CPPUNIT_ASSERT_EQUAL(JID("foo@bar.com/Alice"), p->getTo()); } void testJoin_ChangePresenceDuringJoinDoesNotSendPresenceBeforeJoinSuccess() { MUC::ref testling = createMUC(JID("foo@bar.com")); testling->joinAs("Alice"); presenceSender->sendPresence(Presence::create("Test")); CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(channel->sentStanzas.size())); } void testJoin_ChangePresenceDuringJoinResendsPresenceAfterJoinSuccess() { MUC::ref testling = createMUC(JID("foo@bar.com")); testling->joinAs("Alice"); presenceSender->sendPresence(Presence::create("Test")); receivePresence(JID("foo@bar.com/Rabbit"), "Here"); @@ -109,85 +111,153 @@ class MUCTest : public CppUnit::TestFixture { Presence::ref initialPresence = boost::make_shared<Presence>(); initialPresence->setStatus(""); VCard::ref vcard = boost::make_shared<VCard>(); vcard->setPhoto(createByteArray("15c30080ae98ec48be94bf0e191d43edd06e500a")); initialPresence->addPayload(vcard); CapsInfo::ref caps = boost::make_shared<CapsInfo>(); caps->setNode("http://swift.im"); caps->setVersion("p2UP0DrcVgKM6jJqYN/B92DKK0o="); initialPresence->addPayload(caps); channel->sendPresence(initialPresence); MUC::ref testling = createMUC(JID("test@rooms.swift.im")); testling->joinAs("Test"); Presence::ref serverRespondsLocked = boost::make_shared<Presence>(); serverRespondsLocked->setFrom(JID("test@rooms.swift.im/Test")); serverRespondsLocked->setTo(JID("test@swift.im/6913d576d55f0b67")); serverRespondsLocked->addPayload(vcard); serverRespondsLocked->addPayload(caps); serverRespondsLocked->setStatus(""); MUCUserPayload::ref mucPayload(new MUCUserPayload()); MUCItem myItem; myItem.affiliation = MUCOccupant::Owner; myItem.role = MUCOccupant::Moderator; mucPayload->addItem(myItem); mucPayload->addStatusCode(MUCUserPayload::StatusCode(201)); serverRespondsLocked->addPayload(mucPayload); channel->onPresenceReceived(serverRespondsLocked); CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(channel->sentStanzas.size())); IQ::ref iq = channel->getStanzaAtIndex<IQ>(2); CPPUNIT_ASSERT(iq); CPPUNIT_ASSERT(iq->getPayload<MUCOwnerPayload>()); CPPUNIT_ASSERT(iq->getPayload<MUCOwnerPayload>()->getForm()); CPPUNIT_ASSERT_EQUAL(Form::SubmitType, iq->getPayload<MUCOwnerPayload>()->getForm()->getType()); } + void testNicknameChange() { + MUC::ref testling = createMUC(JID("foo@bar.com")); + // Join as Rabbit + testling->joinAs("Rabbit"); + + // Rabbit joins + Presence::ref rabbitJoins = boost::make_shared<Presence>(); + rabbitJoins->setTo("test@swift.im/6913d576d55f0b67"); + rabbitJoins->setFrom(testling->getJID().toString() + "/Rabbit"); + channel->onPresenceReceived(rabbitJoins); + CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Rabbit")); + + // Alice joins + Presence::ref aliceJoins = boost::make_shared<Presence>(); + aliceJoins->setTo("test@swift.im/6913d576d55f0b67"); + aliceJoins->setFrom(testling->getJID().toString() + "/Alice"); + channel->onPresenceReceived(aliceJoins); + CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Alice")); + + // Change nick to Dodo + testling->changeNickname("Dodo"); + Presence::ref stanza = channel->getStanzaAtIndex<Presence>(1); + CPPUNIT_ASSERT(stanza); + CPPUNIT_ASSERT_EQUAL(std::string("Dodo"), stanza->getTo().getResource()); + + // Alice changes nick to Alice2 + stanza = boost::make_shared<Presence>(); + stanza->setFrom(JID("foo@bar.com/Alice")); + stanza->setTo(JID(router->getJID())); + stanza->setType(Presence::Unavailable); + MUCUserPayload::ref mucPayload(new MUCUserPayload()); + MUCItem myItem; + myItem.affiliation = MUCOccupant::Member; + myItem.nick = "Alice2"; + myItem.role = MUCOccupant::Participant; + mucPayload->addItem(myItem); + mucPayload->addStatusCode(303); + stanza->addPayload(mucPayload); + channel->onPresenceReceived(stanza); + CPPUNIT_ASSERT_EQUAL(1, nickChanges); + CPPUNIT_ASSERT_EQUAL(false, testling->hasOccupant("Alice")); + CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Alice2")); + + // We (Rabbit) change nick to Robot + stanza = boost::make_shared<Presence>(); + stanza->setFrom(JID("foo@bar.com/Rabbit")); + stanza->setTo(JID(router->getJID())); + stanza->setType(Presence::Unavailable); + mucPayload = MUCUserPayload::ref(new MUCUserPayload()); + myItem.affiliation = MUCOccupant::Member; + myItem.nick = "Robot"; + myItem.role = MUCOccupant::Participant; + mucPayload->addItem(myItem); + mucPayload->addStatusCode(303); + stanza->addPayload(mucPayload); + channel->onPresenceReceived(stanza); + CPPUNIT_ASSERT_EQUAL(2, nickChanges); + CPPUNIT_ASSERT_EQUAL(false, testling->hasOccupant("Rabbit")); + CPPUNIT_ASSERT_EQUAL(true, testling->hasOccupant("Robot")); + } + /*void testJoin_Success() { MUC::ref testling = createMUC(JID("foo@bar.com")); testling->onJoinFinished.connect(boost::bind(&MUCTest::handleJoinFinished, this, _1, _2)); testling->joinAs("Alice"); receivePresence(JID("foo@bar.com/Rabbit"), "Here"); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(joinResults.size())); CPPUNIT_ASSERT_EQUAL(std::string("Alice"), joinResults[0].nick); CPPUNIT_ASSERT(joinResults[0].error); } void testJoin_Fail() { //CPPUNIT_ASSERT(!mucRegistry->isMUC(JID("foo@bar.com"))); }*/ private: MUC::ref createMUC(const JID& jid) { - return boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry); + MUC::ref muc = boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry); + muc->onOccupantNicknameChanged.connect(boost::bind(&MUCTest::handleOccupantNicknameChanged, this, _1, _2)); + return muc; } void handleJoinFinished(const std::string& nick, ErrorPayload::ref error) { JoinResult r; r.nick = nick; r.error = error; joinResults.push_back(r); } void receivePresence(const JID& jid, const std::string& status) { Presence::ref p = Presence::create(status); p->setFrom(jid); //MUCUserPayload::ref mucUserPayload = boost::make_shared<MUCUserPayload>(); //mucUserPayload->addItem(item); //p->addPayload(mucUserPayload); channel->onPresenceReceived(p); } + void handleOccupantNicknameChanged(const std::string&, const std::string&) { + nickChanges++; + } + private: DummyStanzaChannel* channel; IQRouter* router; MUCRegistry* mucRegistry; StanzaChannelPresenceSender* stanzaChannelPresenceSender; DirectedPresenceSender* presenceSender; struct JoinResult { std::string nick; ErrorPayload::ref error; }; std::vector<JoinResult> joinResults; + int nickChanges; }; CPPUNIT_TEST_SUITE_REGISTRATION(MUCTest); diff --git a/Swiften/MUC/UnitTest/MockMUC.h b/Swiften/MUC/UnitTest/MockMUC.h index 78c2fb5..8673a90 100644 --- a/Swiften/MUC/UnitTest/MockMUC.h +++ b/Swiften/MUC/UnitTest/MockMUC.h @@ -26,70 +26,71 @@ namespace Swift { class StanzaChannel; class IQRouter; class DirectedPresenceSender; class SWIFTEN_API MockMUC : public MUC{ public: typedef boost::shared_ptr<MockMUC> ref; public: MockMUC(const JID &muc); virtual ~MockMUC(); /** * Cause a user to appear to have entered the room. For testing only. */ void insertOccupant(const MUCOccupant& occupant); /** * Returns the (bare) JID of the MUC. */ virtual JID getJID() const { return ownMUCJID.toBare(); } /** * Returns if the room is unlocked and other people can join the room. * @return True if joinable by others; false otherwise. */ virtual bool isUnlocked() const { return true; } virtual void joinAs(const std::string&) {} virtual void joinWithContextSince(const std::string&, const boost::posix_time::ptime&) {} /*virtual void queryRoomInfo(); */ /*virtual void queryRoomItems(); */ /*virtual std::string getCurrentNick() = 0; */ virtual std::map<std::string, MUCOccupant> getOccupants() const { return occupants_; } + virtual void changeNickname(const std::string&) { } virtual void part() {} /*virtual void handleIncomingMessage(Message::ref message) = 0; */ /** Expose public so it can be called when e.g. user goes offline */ virtual void handleUserLeft(LeavingType) {} /** Get occupant information*/ virtual const MUCOccupant& getOccupant(const std::string&); virtual bool hasOccupant(const std::string&); virtual void kickOccupant(const JID&) {} virtual void changeOccupantRole(const JID&, MUCOccupant::Role); virtual void requestAffiliationList(MUCOccupant::Affiliation) {} virtual void changeAffiliation(const JID&, MUCOccupant::Affiliation); virtual void changeSubject(const std::string&) {} virtual void requestConfigurationForm() {} virtual void configureRoom(Form::ref) {} virtual void cancelConfigureRoom() {} virtual void destroyRoom() {} /** Send an invite for the person to join the MUC */ virtual void invitePerson(const JID&, const std::string&, bool, bool) {} virtual void setCreateAsReservedIfNew() {} virtual void setPassword(const boost::optional<std::string>&) {} protected: virtual bool isFromMUC(const JID& j) const { return ownMUCJID.equals(j, JID::WithoutResource); } virtual const std::string& getOwnNick() const { return ownMUCJID.getResource(); } private: JID ownMUCJID; std::map<std::string, MUCOccupant> occupants_; }; } |