summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Swiften/MIX/MIX.cpp14
-rw-r--r--Swiften/MIX/MIX.h69
-rw-r--r--Swiften/MIX/MIXImpl.cpp97
-rw-r--r--Swiften/MIX/MIXImpl.h61
-rw-r--r--Swiften/MIX/UnitTest/MIXImplTest.cpp312
-rw-r--r--Swiften/SConscript3
6 files changed, 556 insertions, 0 deletions
diff --git a/Swiften/MIX/MIX.cpp b/Swiften/MIX/MIX.cpp
new file mode 100644
index 0000000..f3e3d69
--- /dev/null
+++ b/Swiften/MIX/MIX.cpp
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/MIX/MIX.h>
+
+namespace Swift {
+
+MIX::~MIX() {
+}
+
+}
diff --git a/Swiften/MIX/MIX.h b/Swiften/MIX/MIX.h
new file mode 100644
index 0000000..1398a6e
--- /dev/null
+++ b/Swiften/MIX/MIX.h
@@ -0,0 +1,69 @@
+/*
+ * 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/Form.h>
+#include <Swiften/Elements/MIXJoin.h>
+#include <Swiften/Elements/MIXLeave.h>
+#include <Swiften/Elements/MIXUpdateSubscription.h>
+#include <Swiften/Elements/MIXUserPreference.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 joinChannel(const std::unordered_set<std::string>& nodes) = 0;
+
+ /**
+ * Join Channel with a set of preferences.
+ */
+ virtual void joinChannelWithPreferences(const std::unordered_set<std::string>& nodes, Form::ref form) = 0;
+
+ /**
+ * Update subscription of nodes.
+ */
+ virtual void updateSubscription(const std::unordered_set<std::string>& nodes) = 0;
+
+ /**
+ * Leave a MIX channel and unsubcribe nodes.
+ */
+ virtual void leaveChannel() = 0;
+
+ /**
+ * Request a configuration form for updating preferences.
+ */
+ virtual void requestPreferencesForm() = 0;
+
+ /**
+ * Update preferences after requesting preference form.
+ */
+ virtual void updatePreferences(Form::ref form) = 0;
+
+ public:
+ boost::signals2::signal<void (MIXJoin::ref /* joinResponse */, ErrorPayload::ref /* joinError */)> onJoinResponse;
+ boost::signals2::signal<void (MIXLeave::ref /* leaveResponse */, ErrorPayload::ref /* leaveError */)> onLeaveResponse;
+ boost::signals2::signal<void (MIXUpdateSubscription::ref /* updateResponse */, ErrorPayload::ref /* updateError */)> onSubscriptionUpdateResponse;
+ boost::signals2::signal<void (Form::ref /* preferencesForm */, ErrorPayload::ref /* failedConfiguration */)> onPreferencesFormResponse;
+ boost::signals2::signal<void (MIXUserPreference::ref /* userPreferenceResponse */, ErrorPayload::ref /* failedUpdate */)> onPreferencesUpdateResponse;
+ };
+}
diff --git a/Swiften/MIX/MIXImpl.cpp b/Swiften/MIX/MIXImpl.cpp
new file mode 100644
index 0000000..cd3eb21
--- /dev/null
+++ b/Swiften/MIX/MIXImpl.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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/Client/StanzaChannel.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::joinChannel(const std::unordered_set<std::string>& nodes) {
+ joinChannelWithPreferences(nodes, nullptr);
+}
+
+void MIXImpl::joinChannelWithPreferences(const std::unordered_set<std::string>& nodes, Form::ref form) {
+ auto joinPayload = std::make_shared<MIXJoin>();
+ joinPayload->setChannel(channelJID_);
+ for (auto node : nodes) {
+ joinPayload->addSubscription(node);
+ }
+ if (form) {
+ joinPayload->setForm(form);
+ }
+ 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::handleJoinResponse(MIXJoin::ref payload, ErrorPayload::ref error) {
+ onJoinResponse(payload, error);
+}
+
+void MIXImpl::updateSubscription(const std::unordered_set<std::string>& nodes) {
+ auto updateSubscriptionPayload = std::make_shared<MIXUpdateSubscription>();
+ updateSubscriptionPayload->setSubscriptions(nodes);
+ auto request = std::make_shared<GenericRequest<MIXUpdateSubscription>>(IQ::Set, channelJID_, updateSubscriptionPayload, iqRouter_);
+ request->onResponse.connect(boost::bind(&MIXImpl::handleUpdateSubscriptionResponse, this, _1, _2));
+ request->send();
+}
+
+void MIXImpl::handleUpdateSubscriptionResponse(MIXUpdateSubscription::ref payload, ErrorPayload::ref error) {
+ onSubscriptionUpdateResponse(payload, error);
+}
+
+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) {
+ onLeaveResponse(payload, error);
+}
+
+void MIXImpl::requestPreferencesForm() {
+ auto prefPayload = std::make_shared<MIXUserPreference>();
+ auto request = std::make_shared<GenericRequest<MIXUserPreference>>(IQ::Get, channelJID_, prefPayload, iqRouter_);
+ request->onResponse.connect(boost::bind(&MIXImpl::handlePreferencesFormReceived, this, _1, _2));
+ request->send();
+}
+
+void MIXImpl::handlePreferencesFormReceived(MIXUserPreference::ref payload, ErrorPayload::ref error) {
+ Form::ref form = nullptr;
+ if (payload) {
+ form = payload->getData();
+ }
+ onPreferencesFormResponse(form, error);
+}
+
+void MIXImpl::handlePreferencesResultReceived(MIXUserPreference::ref payload, ErrorPayload::ref error) {
+ onPreferencesUpdateResponse(payload, error);
+}
+
+void MIXImpl::updatePreferences(Form::ref form) {
+ auto prefPayload = std::make_shared<MIXUserPreference>();
+ prefPayload->setData(form);
+ auto request = std::make_shared<GenericRequest<MIXUserPreference>>(IQ::Set, channelJID_, prefPayload, iqRouter_);
+ request->onResponse.connect(boost::bind(&MIXImpl::handlePreferencesResultReceived, this, _1, _2));
+ request->send();
+}
+
+}
diff --git a/Swiften/MIX/MIXImpl.h b/Swiften/MIX/MIXImpl.h
new file mode 100644
index 0000000..58b33f4
--- /dev/null
+++ b/Swiften/MIX/MIXImpl.h
@@ -0,0 +1,61 @@
+/*
+ * 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 joinChannel(const std::unordered_set<std::string>& nodes) override;
+
+ virtual void joinChannelWithPreferences(const std::unordered_set<std::string>& nodes, Form::ref form) override;
+
+ virtual void updateSubscription(const std::unordered_set<std::string>& nodes) override;
+
+ virtual void leaveChannel() override;
+
+ virtual void requestPreferencesForm() override;
+
+ virtual void updatePreferences(Form::ref form) override;
+
+ private:
+ void handleJoinResponse(MIXJoin::ref, ErrorPayload::ref);
+ void handleLeaveResponse(MIXLeave::ref, ErrorPayload::ref);
+ void handleUpdateSubscriptionResponse(MIXUpdateSubscription::ref, ErrorPayload::ref);
+ void handlePreferencesFormReceived(MIXUserPreference::ref, ErrorPayload::ref);
+ void handlePreferencesResultReceived(MIXUserPreference::ref /*payload*/, ErrorPayload::ref error);
+
+ private:
+ JID ownJID_;
+ JID channelJID_;
+ IQRouter* iqRouter_;
+ };
+}
diff --git a/Swiften/MIX/UnitTest/MIXImplTest.cpp b/Swiften/MIX/UnitTest/MIXImplTest.cpp
new file mode 100644
index 0000000..05dde17
--- /dev/null
+++ b/Swiften/MIX/UnitTest/MIXImplTest.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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/Elements/FormField.h>
+#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->onJoinResponse.connect(boost::bind(&MIXImplTest::handleJoin, this, _1, _2));
+ mix->onLeaveResponse.connect(boost::bind(&MIXImplTest::handleLeave, this, _1, _2));
+ mix->onSubscriptionUpdateResponse.connect(boost::bind(&MIXImplTest::handleSubscriptionUpdate, this, _1, _2));
+ mix->onPreferencesFormResponse.connect(boost::bind(&MIXImplTest::handlePreferencesForm, this, _1, _2));
+ return mix;
+ }
+
+ void handleJoin(MIXJoin::ref joinPayload, ErrorPayload::ref error) {
+ if (joinPayload) {
+ ASSERT_FALSE(error);
+ ASSERT_TRUE(joinPayload->getJID());
+ ASSERT_EQ(*joinPayload->getJID(), JID("123456#coven@mix.shakespeare.example"));
+ if (joinPayload->getForm()) {
+ preferenceForm_ = joinPayload->getForm();
+ }
+ ++successfulJoins_;
+ subscribedNodes_ = joinPayload->getSubscriptions();
+ }
+ }
+
+ void handleLeave(MIXLeave::ref leavePayload, ErrorPayload::ref error) {
+ ASSERT_TRUE(leavePayload);
+ ASSERT_FALSE(error);
+ ASSERT_EQ(static_cast<int>(0), subscribedNodes_.size());
+ }
+
+ void handleSubscriptionUpdate(MIXUpdateSubscription::ref payload, ErrorPayload::ref error) {
+ ASSERT_TRUE(payload);
+ ASSERT_FALSE(error);
+ if (payload) {
+ for (auto node : payload->getSubscriptions()) {
+ subscribedNodes_.insert(node);
+ }
+ }
+ }
+
+ void handlePreferencesForm(Form::ref form, ErrorPayload::ref error) {
+ ASSERT_FALSE(error);
+ if (form) {
+ preferenceForm_ = form;
+ }
+ }
+
+ IQ::ref createJoinResult(const std::unordered_set<std::string>& nodes, Form::ref form) {
+ auto joinResultPayload = std::make_shared<MIXJoin>();
+ for (auto node : nodes) {
+ joinResultPayload->addSubscription(node);
+ }
+ if (form) {
+ joinResultPayload->setForm(form);
+ }
+ 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 createError() {
+ 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();
+ }
+
+ JID ownJID_;
+ JID channelJID_;
+ DummyStanzaChannel* channel_;
+ IQRouter* router_;
+ int successfulJoins_;
+ Form::ref preferenceForm_;
+ std::unordered_set<std::string> subscribedNodes_;
+};
+
+TEST_F(MIXImplTest, testJoinError) {
+ MIX::ref testling = createMIXClient();
+ testling->joinChannel(std::unordered_set<std::string>());
+
+ 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(createError());
+ 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->joinChannel(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, nullptr));
+ 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->joinChannel(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, nullptr));
+ 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, testUpdateSubscription) {
+ 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"));
+
+ testling->joinChannel(nodes);
+
+ ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
+ ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));
+
+ channel_->onIQReceived(createJoinResult(nodes, nullptr));
+ ASSERT_EQ(static_cast<int>(1), successfulJoins_);
+ ASSERT_EQ(static_cast<int>(2), subscribedNodes_.size());
+
+ nodes.clear();
+ nodes.insert(std::string("urn:xmpp:mix:nodes:participants"));
+ nodes.insert(std::string("urn:xmpp:mix:nodes:config"));
+
+ testling->updateSubscription(nodes);
+
+ ASSERT_EQ(2, static_cast<int>(channel_->sentStanzas.size()));
+ ASSERT_TRUE(channel_->isRequestAtIndex<MIXUpdateSubscription>(1, channelJID_, IQ::Set));
+
+ // fake response
+ auto subscriptionUpdate = std::make_shared<MIXUpdateSubscription>();
+ subscriptionUpdate->setSubscriptions(nodes);
+ subscriptionUpdate->setJID(JID("hag66@shakespeare.example"));
+ auto response = IQ::createResult(ownJID_, channel_->sentStanzas[1]->getTo(), channel_->sentStanzas[1]->getID(), subscriptionUpdate);
+
+ channel_->onIQReceived(response);
+ ASSERT_EQ(static_cast<int>(4), subscribedNodes_.size());
+ ASSERT_TRUE(hasSubscription(std::string("urn:xmpp:mix:nodes:participants")));
+ ASSERT_TRUE(hasSubscription(std::string("urn:xmpp:mix:nodes:config")));
+ ASSERT_TRUE(hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+}
+
+TEST_F(MIXImplTest, testJoinWithPreference) {
+ 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"));
+
+ auto parameters = std::make_shared<Form>();
+ parameters->setType(Form::Type::SubmitType);
+
+ auto fieldType = std::make_shared<FormField>(FormField::HiddenType);
+ fieldType->setName("FORM_TYPE");
+ fieldType->addValue("urn:xmpp:mix:0");
+ parameters->addField(fieldType);
+
+ auto fieldJIDVisibility = std::make_shared<FormField>();
+ fieldJIDVisibility->setName("JID Visibility");
+ fieldJIDVisibility->addValue("never");
+ parameters->addField(fieldJIDVisibility);
+
+ testling->joinChannelWithPreferences(nodes, parameters);
+
+ ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
+ ASSERT_TRUE(channel_->isRequestAtIndex<MIXJoin>(0, ownJID_.toBare(), IQ::Set));
+
+ //fake response
+ auto responseForm = std::make_shared<Form>();
+ responseForm->setType(Form::Type::ResultType);
+
+ auto fieldTypeResponse = std::make_shared<FormField>(FormField::HiddenType);
+ fieldTypeResponse->setName("FORM_TYPE");
+ fieldTypeResponse->addValue("urn:xmpp:mix:0");
+ responseForm->addField(fieldTypeResponse);
+
+ auto fieldJIDVisibilityResponse = std::make_shared<FormField>();
+ fieldJIDVisibilityResponse->setName("JID Visibility");
+ fieldJIDVisibilityResponse->addValue("never");
+ responseForm->addField(fieldJIDVisibilityResponse);
+
+ auto fieldprivateMessagesResponse = std::make_shared<FormField>();
+ fieldprivateMessagesResponse->setName("Private Messages");
+ fieldprivateMessagesResponse->addValue("allow");
+ responseForm->addField(fieldprivateMessagesResponse);
+
+ auto vCardResponse = std::make_shared<FormField>();
+ vCardResponse->setName("vCard");
+ vCardResponse->addValue("block");
+ responseForm->addField(vCardResponse);
+
+ channel_->onIQReceived(createJoinResult(nodes, responseForm));
+ ASSERT_EQ(static_cast<int>(1), successfulJoins_);
+ ASSERT_EQ(static_cast<int>(2), subscribedNodes_.size());
+ ASSERT_TRUE(preferenceForm_);
+
+ ASSERT_TRUE(preferenceForm_->getField("JID Visibility"));
+ ASSERT_EQ(std::string("never"), preferenceForm_->getField("JID Visibility")->getTextSingleValue());
+}
+
+TEST_F(MIXImplTest, preferenceFormRequest) {
+ MIX::ref testling = createMIXClient();
+ testling->requestPreferencesForm();
+
+ ASSERT_EQ(1, static_cast<int>(channel_->sentStanzas.size()));
+ ASSERT_TRUE(channel_->isRequestAtIndex<MIXUserPreference>(0, channelJID_, IQ::Get));
+
+ //fake response
+ auto responseForm = std::make_shared<Form>();
+ responseForm->setType(Form::Type::FormType);
+
+ auto fieldTypeResponse = std::make_shared<FormField>(FormField::HiddenType);
+ fieldTypeResponse->setName("FORM_TYPE");
+ fieldTypeResponse->addValue("urn:xmpp:mix:0");
+ responseForm->addField(fieldTypeResponse);
+
+ auto preferenceResponse = std::make_shared<MIXUserPreference>();
+ preferenceResponse->setData(responseForm);
+
+ channel_->onIQReceived(IQ::createResult(ownJID_, channel_->sentStanzas[0]->getTo(), channel_->sentStanzas[0]->getID(), preferenceResponse));
+ ASSERT_TRUE(preferenceForm_);
+}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 7f2a92b..d028a0c 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -164,6 +164,8 @@ if env["SCONS_STAGE"] == "build" :
"Elements/IsodeIQDelegation.cpp",
"Entity/Entity.cpp",
"Entity/PayloadPersister.cpp",
+ "MIX/MIX.cpp",
+ "MIX/MIXImpl.cpp",
"MUC/MUC.cpp",
"MUC/MUCImpl.cpp",
"MUC/MUCManager.cpp",
@@ -415,6 +417,7 @@ if env["SCONS_STAGE"] == "build" :
File("LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp"),
File("LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp"),
File("LinkLocal/UnitTest/LinkLocalServiceTest.cpp"),
+ File("MIX/UnitTest/MIXImplTest.cpp"),
File("MUC/UnitTest/MUCTest.cpp"),
File("MUC/UnitTest/MockMUC.cpp"),
File("Network/UnitTest/HostAddressTest.cpp"),