blob: 1e8be42e61611749b61433c66ba4a05e0d92d199 [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "buffet/notification/xmpp_iq_stanza_handler.h"
#include <map>
#include <memory>
#include <chromeos/bind_lambda.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "buffet/notification/xml_node.h"
#include "buffet/notification/xmpp_channel.h"
#include "buffet/notification/xmpp_stream_parser.h"
using testing::Invoke;
using testing::Return;
using testing::_;
namespace buffet {
namespace {
// Mock-like task runner that allow the tests to inspect the calls to
// TaskRunner::PostDelayedTask and verify the delays.
class TestTaskRunner : public base::SingleThreadTaskRunner {
public:
TestTaskRunner() = default;
MOCK_METHOD3(PostDelayedTask, bool(const tracked_objects::Location&,
const base::Closure&,
base::TimeDelta));
MOCK_METHOD3(PostNonNestableDelayedTask,
bool(const tracked_objects::Location&,
const base::Closure&,
base::TimeDelta));
bool RunsTasksOnCurrentThread() const { return true; }
private:
DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
};
class MockXmppChannelInterface : public XmppChannelInterface {
public:
MockXmppChannelInterface() = default;
MOCK_METHOD1(SendMessage, void(const std::string&));
private:
DISALLOW_COPY_AND_ASSIGN(MockXmppChannelInterface);
};
// Simple class that allows to parse XML from string to XmlNode.
class XmlParser : public XmppStreamParser::Delegate {
public:
std::unique_ptr<XmlNode> Parse(const std::string& xml) {
parser_.ParseData(xml);
return std::move(node_);
}
private:
// Overrides from XmppStreamParser::Delegate.
void OnStreamStart(const std::string& node_name,
std::map<std::string, std::string> attributes) override {
node_.reset(new XmlNode{node_name, std::move(attributes)});
}
void OnStreamEnd(const std::string& node_name) override {}
void OnStanza(std::unique_ptr<XmlNode> stanza) override {
node_->AddChild(std::move(stanza));
}
std::unique_ptr<XmlNode> node_;
XmppStreamParser parser_{this};
};
class MockResponseReceiver {
public:
MOCK_METHOD2(OnResponse, void(int id, const std::string&));
IqStanzaHandler::ResponseCallback callback(int id) {
return base::Bind(&MockResponseReceiver::OnResponseCallback,
base::Unretained(this), id);
}
private:
void OnResponseCallback(int id, std::unique_ptr<XmlNode> response) {
OnResponse(id, response->children().front()->name());
}
};
// Functor to call the |task| immediately.
struct TaskInvoker {
bool operator()(const tracked_objects::Location& location,
const base::Closure& task,
base::TimeDelta delay) {
task.Run();
return true;
}
};
} // anonymous namespace
class IqStanzaHandlerTest : public testing::Test {
public:
void SetUp() override {
task_runner_ = new TestTaskRunner;
iq_stanza_handler_.reset(
new IqStanzaHandler{&mock_xmpp_channel_, task_runner_});
}
testing::StrictMock<MockXmppChannelInterface> mock_xmpp_channel_;
scoped_refptr<TestTaskRunner> task_runner_;
std::unique_ptr<IqStanzaHandler> iq_stanza_handler_;
MockResponseReceiver receiver_;
TaskInvoker task_invoker_;
};
TEST_F(IqStanzaHandlerTest, SendRequest) {
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.WillRepeatedly(Return(true));
std::string expected_msg = "<iq id='1' type='set'><body/></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
iq_stanza_handler_->SendRequest("set", "", "", "<body/>", {}, {});
expected_msg = "<iq id='2' type='get'><body/></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
iq_stanza_handler_->SendRequest("get", "", "", "<body/>", {}, {});
expected_msg = "<iq id='3' type='query' from='foo@bar'><body/></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
iq_stanza_handler_->SendRequest("query", "foo@bar", "", "<body/>", {}, {});
expected_msg = "<iq id='4' type='query' to='foo@bar'><body/></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
iq_stanza_handler_->SendRequest("query", "", "foo@bar", "<body/>", {}, {});
expected_msg = "<iq id='5' type='query' from='foo@bar' to='baz'><body/></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
iq_stanza_handler_->SendRequest("query", "foo@bar", "baz", "<body/>", {}, {});
}
TEST_F(IqStanzaHandlerTest, UnsupportedIqRequest) {
// Server IQ requests are not supported for now. Expect an error response.
std::string expected_msg =
"<iq id='1' type='error'><error type='modify'>"
"<feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
"</error></iq>";
EXPECT_CALL(mock_xmpp_channel_, SendMessage(expected_msg)).Times(1);
auto request = XmlParser{}.Parse("<iq id='1' type='set'><foo/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
}
TEST_F(IqStanzaHandlerTest, UnknownResponseId) {
// No requests with ID=100 have been previously sent.
auto request = XmlParser{}.Parse("<iq id='100' type='result'><foo/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
}
TEST_F(IqStanzaHandlerTest, SequentialResponses) {
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.Times(2).WillRepeatedly(Return(true));
EXPECT_CALL(mock_xmpp_channel_, SendMessage(_)).Times(2);
iq_stanza_handler_->SendRequest("set", "", "", "<body/>",
receiver_.callback(1), {});
iq_stanza_handler_->SendRequest("get", "", "", "<body/>",
receiver_.callback(2), {});
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.Times(2).WillRepeatedly(Invoke(task_invoker_));
EXPECT_CALL(receiver_, OnResponse(1, "foo"));
auto request = XmlParser{}.Parse("<iq id='1' type='result'><foo/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
EXPECT_CALL(receiver_, OnResponse(2, "bar"));
request = XmlParser{}.Parse("<iq id='2' type='result'><bar/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
}
TEST_F(IqStanzaHandlerTest, OutOfOrderResponses) {
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.Times(2).WillRepeatedly(Return(true));
EXPECT_CALL(mock_xmpp_channel_, SendMessage(_)).Times(2);
iq_stanza_handler_->SendRequest("set", "", "", "<body/>",
receiver_.callback(1), {});
iq_stanza_handler_->SendRequest("get", "", "", "<body/>",
receiver_.callback(2), {});
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.Times(2).WillRepeatedly(Invoke(task_invoker_));
EXPECT_CALL(receiver_, OnResponse(2, "bar"));
auto request = XmlParser{}.Parse("<iq id='2' type='result'><bar/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
EXPECT_CALL(receiver_, OnResponse(1, "foo"));
request = XmlParser{}.Parse("<iq id='1' type='result'><foo/></iq>");
EXPECT_TRUE(iq_stanza_handler_->HandleIqStanza(std::move(request)));
}
TEST_F(IqStanzaHandlerTest, RequestTimeout) {
EXPECT_CALL(*task_runner_, PostDelayedTask(_, _, _))
.WillOnce(Invoke(task_invoker_));
bool called = false;
auto on_timeout = [&called]() { called = true; };
EXPECT_CALL(mock_xmpp_channel_, SendMessage(_)).Times(1);
EXPECT_FALSE(called);
iq_stanza_handler_->SendRequest("set", "", "", "<body/>", {},
base::Bind(on_timeout));
EXPECT_TRUE(called);
}
} // namespace buffet