Skip to content

Commit

Permalink
Add methods for requesting VCard of a channel participant
Browse files Browse the repository at this point in the history
License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

Test-Information:
Tests added for requesting VCard as in XEP-0369, which passes.
Tested on Ubuntu 16.04 LTS.

Change-Id: I08f550c71066e373955439ca81d61a26b7c254d9
  • Loading branch information
tarun018 committed Jul 18, 2017
1 parent f0db4d3 commit 362a1ab
Show file tree
Hide file tree
Showing 4 changed files with 392 additions and 0 deletions.
48 changes: 48 additions & 0 deletions Swiften/MIX/MIX.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2017 Tarun Gupta
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/

#pragma once

#include <memory>
#include <string>
#include <unordered_set>

#include <boost/signals2.hpp>

#include <Swiften/Base/API.h>
#include <Swiften/JID/JID.h>
#include <Swiften/Elements/MIXJoin.h>
#include <Swiften/Elements/MIXLeave.h>
#include <Swiften/Elements/VCard.h>
#include <Swiften/Elements/ErrorPayload.h>

namespace Swift {
class SWIFTEN_API MIX {
public:
using ref = std::shared_ptr<MIX>;

public:
virtual ~MIX();

/**
* Join a MIX channel and subscribe to nodes.
*/
virtual void joinChannelAndSubscribe(const std::unordered_set<std::string>& nodes) = 0;
virtual void joinChannel() = 0;

virtual void leaveChannel() = 0;

virtual void requestVCard(const JID &participant) = 0;

public:
boost::signals2::signal<void (MIXJoin::ref /* joinResponse */)> onJoinComplete;
boost::signals2::signal<void (ErrorPayload::ref /* joinError */)> onJoinFailed;
boost::signals2::signal<void (MIXLeave::ref /* leaveResponse */)> onLeaveComplete;
boost::signals2::signal<void (ErrorPayload::ref /* leaveError */)> onLeaveFailed;
boost::signals2::signal<void (VCard::ref /* vCard */)> onVCardReceived;
boost::signals2::signal<void (ErrorPayload::ref /* vCardRequestError */)> onVCardRequestFailed;
};
}
77 changes: 77 additions & 0 deletions Swiften/MIX/MIXImpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2017 Tarun Gupta
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/

#include <Swiften/MIX/MIXImpl.h>

#include <Swiften/Elements/IQ.h>
#include <Swiften/Queries/GenericRequest.h>
#include <Swiften/Queries/IQRouter.h>

namespace Swift {

MIXImpl::MIXImpl(const JID &ownJID, const JID &channelJID, IQRouter* iqRouter) : ownJID_(ownJID), channelJID_(channelJID), iqRouter_(iqRouter) {

}

MIXImpl::~MIXImpl() {

}

void MIXImpl::joinChannelAndSubscribe(const std::unordered_set<std::string>& nodes) {
auto joinPayload = std::make_shared<MIXJoin>();
joinPayload->setChannel(channelJID_);
for (auto node : nodes) {
joinPayload->addSubscription(node);
}
auto request = std::make_shared<GenericRequest<MIXJoin> >(IQ::Set, getJID(), joinPayload, iqRouter_);
request->onResponse.connect(boost::bind(&MIXImpl::handleJoinResponse, this, _1, _2));
request->send();
}

void MIXImpl::joinChannel() {
joinChannelAndSubscribe(std::unordered_set<std::string>());
}

void MIXImpl::handleJoinResponse(MIXJoin::ref payload, ErrorPayload::ref error) {
if (error) {
onJoinFailed(error);
} else {
onJoinComplete(payload);
}
}

void MIXImpl::leaveChannel() {
auto leavePayload = std::make_shared<MIXLeave>();
leavePayload->setChannel(channelJID_);
auto request = std::make_shared<GenericRequest<MIXLeave> >(IQ::Set, getJID(), leavePayload, iqRouter_);
request->onResponse.connect(boost::bind(&MIXImpl::handleLeaveResponse, this, _1, _2));
request->send();
}

void MIXImpl::handleLeaveResponse(MIXLeave::ref payload, ErrorPayload::ref error) {
if (error) {
onLeaveFailed(error);
} else {
onLeaveComplete(payload);
}
}

void MIXImpl::requestVCard(const JID &participant) {
auto vCardPayload = std::make_shared<VCard>();
auto request = std::make_shared<GenericRequest<VCard> >(IQ::Get, participant, vCardPayload, iqRouter_);
request->onResponse.connect(boost::bind(&MIXImpl::handleVCardReceived, this, _1, _2));
request->send();
}

void MIXImpl::handleVCardReceived(VCard::ref payload, ErrorPayload::ref error) {
if (error) {
onVCardRequestFailed(error);
} else {
onVCardReceived(payload);
}
}

}
54 changes: 54 additions & 0 deletions Swiften/MIX/MIXImpl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2017 Tarun Gupta
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/

#pragma once

#include <Swiften/MIX/MIX.h>

namespace Swift {
class StanzaChannel;
class IQRouter;

class SWIFTEN_API MIXImpl : public MIX {
public:
using ref = std::shared_ptr<MIXImpl>;

public:
MIXImpl(const JID &ownJID, const JID &channelJID, IQRouter* iqRouter);
virtual ~MIXImpl();

/**
* Returns the (bare) JID of the user.
*/
virtual JID getJID() const {
return ownJID_.toBare();
}

/**
* Returns the JID of MIX channel.
*/
virtual JID getChannelJID() const {
return channelJID_;
}

virtual void joinChannelAndSubscribe(const std::unordered_set<std::string>& nodes) override;
virtual void joinChannel() override;

virtual void leaveChannel() override;

virtual void requestVCard(const JID &participant) override;

private:
void handleJoinResponse(MIXJoin::ref, ErrorPayload::ref);
void handleLeaveResponse(MIXLeave::ref, ErrorPayload::ref);
void handleVCardReceived(VCard::ref payload, ErrorPayload::ref error);

private:
JID ownJID_;
JID channelJID_;
IQRouter* iqRouter_;
};
}
213 changes: 213 additions & 0 deletions Swiften/MIX/UnitTest/MIXImplTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright (c) 2017 Tarun Gupta
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/

#include <gtest/gtest.h>

#include <boost/bind.hpp>

#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/MIX/MIXImpl.h>
#include <Swiften/Queries/IQRouter.h>

using namespace Swift;

class MIXImplTest : public ::testing::Test {

protected:
void SetUp() {
ownJID_ = JID("hag66@shakespeare.example/UUID-a1j/7533");
channelJID_ = JID("coven@mix.shakespeare.example");
channel_ = new DummyStanzaChannel();
router_ = new IQRouter(channel_);
successfulJoins_ = 0;
}

void TearDown() {
delete router_;
delete channel_;
}

MIX::ref createMIXClient() {
auto mix = std::make_shared<MIXImpl>(ownJID_, channelJID_, router_);
mix->onJoinComplete.connect(boost::bind(&MIXImplTest::handleJoinComplete, this, _1));
mix->onLeaveComplete.connect(boost::bind(&MIXImplTest::handleLeaveComplete, this, _1));
mix->onVCardReceived.connect(boost::bind(&MIXImplTest::handleVCard, this, _1));
return mix;
}

void handleJoinComplete(MIXJoin::ref joinPayload) {
ASSERT_TRUE(joinPayload);
ASSERT_TRUE(joinPayload->getJID());
ASSERT_EQ(*joinPayload->getJID(), JID("123456#coven@mix.shakespeare.example"));
++successfulJoins_;
subscribedNodes_ = joinPayload->getSubscriptions();
}

void handleLeaveComplete(MIXLeave::ref leavePayload) {
ASSERT_TRUE(leavePayload);
ASSERT_EQ(static_cast<int>(0), subscribedNodes_.size());
}

void handleVCard(VCard::ref payload) {
ASSERT_TRUE(payload);
vCardResult_ = payload;
}

IQ::ref createJoinResult(const std::unordered_set<std::string>& nodes) {
auto joinResultPayload = std::make_shared<MIXJoin>();
for (auto node : nodes) {
joinResultPayload->addSubscription(node);
}
joinResultPayload->setJID(JID("123456#coven@mix.shakespeare.example"));
return IQ::createResult(ownJID_, channel_->sentStanzas[0]->getTo(), channel_->sentStanzas[0]->getID(), joinResultPayload);
}

IQ::ref createLeaveResult() {
auto leaveResultPayload = std::make_shared<MIXLeave>();
return IQ::createResult(ownJID_, channel_->sentStanzas[0]->getTo(), channel_->sentStanzas[0]->getID(), leaveResultPayload);
}

IQ::ref createJoinError() {
return IQ::createError(ownJID_, channel_->sentStanzas[0]->getTo(), channel_->sentStanzas[0]->getID());
}

bool hasSubscription(const std::string& value) {
return std::find(subscribedNodes_.begin(), subscribedNodes_.end(), value) != subscribedNodes_.end();
}

IQ::ref createVCardResult() {
auto vCard = std::make_shared<VCard>();
vCard->setFullName("Peter Saint-Andre");
vCard->setFamilyName("Saint-Andre");
vCard->setGivenName("Peter");
vCard->setNickname("stpeter");
vCard->addURL("http://www.xmpp.org/xsf/people/stpeter.shtml");
return IQ::createResult(ownJID_, channel_->sentStanzas[0]->getTo(), channel_->sentStanzas[0]->getID(), vCard);
}

JID ownJID_;
JID channelJID_;
DummyStanzaChannel* channel_;
IQRouter* router_;
int successfulJoins_;
std::unordered_set<std::string> subscribedNodes_;
VCard::ref vCardResult_;
};

TEST_F(MIXImplTest, testJoinChannelOnly) {
MIX::ref testling = createMIXClient();
testling->joinChannel();

ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));

auto iq = channel_->getStanzaAtIndex<IQ>(0);
ASSERT_TRUE(iq);
ASSERT_TRUE(iq->getPayload<MIXJoin>());
ASSERT_FALSE(iq->getPayload<MIXJoin>()->getForm());
ASSERT_EQ(static_cast<size_t>(0), iq->getPayload<MIXJoin>()->getSubscriptions().size());

channel_->onIQReceived(createJoinResult(std::unordered_set<std::string>()));
ASSERT_EQ(static_cast<int>(1), successfulJoins_);
ASSERT_EQ(static_cast<int>(0), subscribedNodes_.size());
}

TEST_F(MIXImplTest, testJoinError) {
MIX::ref testling = createMIXClient();
testling->joinChannel();

ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));

auto iq = channel_->getStanzaAtIndex<IQ>(0);
ASSERT_TRUE(iq);
ASSERT_TRUE(iq->getPayload<MIXJoin>());
ASSERT_FALSE(iq->getPayload<MIXJoin>()->getForm());
ASSERT_EQ(static_cast<size_t>(0), iq->getPayload<MIXJoin>()->getSubscriptions().size());

channel_->onIQReceived(createJoinError());
ASSERT_EQ(static_cast<int>(0), successfulJoins_);
ASSERT_EQ(static_cast<int>(0), subscribedNodes_.size());
}

TEST_F(MIXImplTest, testJoinWithAllSubscriptions) {
MIX::ref testling = createMIXClient();
std::unordered_set<std::string> nodes;
nodes.insert(std::string("urn:xmpp:mix:nodes:messages"));
nodes.insert(std::string("urn:xmpp:mix:nodes:presence"));
nodes.insert(std::string("urn:xmpp:mix:nodes:participants"));
nodes.insert(std::string("urn:xmpp:mix:nodes:config"));

testling->joinChannelAndSubscribe(nodes);

ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));

auto iq = channel_->getStanzaAtIndex<IQ>(0);
ASSERT_TRUE(iq);
ASSERT_TRUE(iq->getPayload<MIXJoin>());
ASSERT_FALSE(iq->getPayload<MIXJoin>()->getForm());
ASSERT_EQ(static_cast<size_t>(4), iq->getPayload<MIXJoin>()->getSubscriptions().size());

channel_->onIQReceived(createJoinResult(nodes));
ASSERT_EQ(static_cast<int>(1), successfulJoins_);
ASSERT_EQ(static_cast<int>(4), subscribedNodes_.size());
}

TEST_F(MIXImplTest, testJoinWithSomeSubscriptions) {
MIX::ref testling = createMIXClient();
std::unordered_set<std::string> nodes;
nodes.insert(std::string("urn:xmpp:mix:nodes:messages"));
nodes.insert(std::string("urn:xmpp:mix:nodes:presence"));
nodes.insert(std::string("urn:xmpp:mix:nodes:participants"));
nodes.insert(std::string("urn:xmpp:mix:nodes:config"));

testling->joinChannelAndSubscribe(nodes);

ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));

auto iq = channel_->getStanzaAtIndex<IQ>(0);
ASSERT_TRUE(iq);
ASSERT_TRUE(iq->getPayload<MIXJoin>());
ASSERT_FALSE(iq->getPayload<MIXJoin>()->getForm());
ASSERT_EQ(static_cast<size_t>(4), iq->getPayload<MIXJoin>()->getSubscriptions().size());

std::unordered_set<std::string> subscribedTo;
subscribedTo.insert(std::string("urn:xmpp:mix:nodes:messages"));

channel_->onIQReceived(createJoinResult(subscribedTo));
ASSERT_EQ(static_cast<int>(1), successfulJoins_);
ASSERT_EQ(static_cast<int>(1), subscribedNodes_.size());
ASSERT_TRUE(hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
}


TEST_F(MIXImplTest, testLeaveChannel) {
MIX::ref testling = createMIXClient();
testling->leaveChannel();
ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<MIXLeave>(0, ownJID_.toBare(), IQ::Set));

auto iq = channel_->getStanzaAtIndex<IQ>(0);
ASSERT_TRUE(iq);
ASSERT_TRUE(iq->getPayload<MIXLeave>());
ASSERT_TRUE(iq->getPayload<MIXLeave>()->getChannel());

channel_->onIQReceived(createLeaveResult());
}

}

TEST_F(MIXImplTest, testRequestVCard) {
MIX::ref testling = createMIXClient(ownJID_);
testling->requestVCard(JID("989898#coven@mix.shakespeare.example"));
ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
ASSERT_TRUE(channel_->isRequestAtIndex<VCard>(0, JID("989898#coven@mix.shakespeare.example"), IQ::Get));

channel_->onIQReceived(createVCardResult());
ASSERT_TRUE(vCardResult_);
ASSERT_EQ(vCardResult_->getFullName(), std::string("Peter Saint-Andre"));

0 comments on commit 362a1ab

Please sign in to comment.