blob: dfa2a79ea265164cc6bd5d7f67d32ad24297ab26 [file] [log] [blame]
// Copyright 2015 The Weave 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 "src/notification/xmpp_channel.h"
#include <algorithm>
#include <queue>
#include <gtest/gtest.h>
#include <weave/provider/test/fake_task_runner.h>
#include <weave/provider/test/mock_network.h>
#include <weave/test/fake_stream.h>
#include "src/bind_lambda.h"
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
using testing::WithArgs;
namespace weave {
namespace {
constexpr char kAccountName[] = "Account@Name";
constexpr char kAccessToken[] = "AccessToken";
constexpr char kEndpoint[] = "endpoint:456";
constexpr char kStartStreamMessage[] =
"<stream:stream to='clouddevices.gserviceaccount.com' "
"xmlns:stream='http://etherx.jabber.org/streams' xml:lang='*' "
"version='1.0' xmlns='jabber:client'>";
constexpr char kStartStreamResponse[] =
"<stream:stream from=\"clouddevices.gserviceaccount.com\" "
"id=\"0CCF520913ABA04B\" version=\"1.0\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
"xmlns=\"jabber:client\">";
constexpr char kAuthenticationMessage[] =
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='X-OAUTH2' "
"auth:service='oauth2' auth:allow-non-google-login='true' "
"auth:client-uses-full-bind-result='true' "
"xmlns:auth='http://www.google.com/talk/protocol/auth'>"
"AEFjY291bnRATmFtZQBBY2Nlc3NUb2tlbg==</auth>";
constexpr char kConnectedResponse[] =
"<stream:features><mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
"<mechanism>X-OAUTH2</mechanism>"
"<mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>";
constexpr char kAuthenticationSucceededResponse[] =
"<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>";
constexpr char kAuthenticationFailedResponse[] =
"<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><not-authorized/>"
"</failure>";
constexpr char kRestartStreamResponse[] =
"<stream:features><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
"<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
"</stream:features>";
constexpr char kBindResponse[] =
"<iq id=\"1\" type=\"result\">"
"<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
"<jid>110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com"
"/19853128</jid></bind></iq>";
constexpr char kSessionResponse[] = "<iq type=\"result\" id=\"2\"/>";
constexpr char kSubscribedResponse[] =
"<iq to=\""
"110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com/"
"19853128\" from=\""
"110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com\" "
"id=\"3\" type=\"result\"/>";
constexpr char kBindMessage[] =
"<iq id='1' type='set'><bind "
"xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
constexpr char kSessionMessage[] =
"<iq id='2' type='set'><session "
"xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>";
constexpr char kSubscribeMessage[] =
"<iq id='3' type='set' to='Account@Name'>"
"<subscribe xmlns='google:push'><item channel='cloud_devices' from=''/>"
"</subscribe></iq>";
} // namespace
class FakeXmppChannel : public XmppChannel {
public:
explicit FakeXmppChannel(provider::TaskRunner* task_runner,
provider::Network* network)
: XmppChannel{kAccountName, kAccessToken, kEndpoint, task_runner,
network},
stream_{new test::FakeStream{task_runner_}},
fake_stream_{stream_.get()} {}
void Connect(const base::Callback<void(std::unique_ptr<Stream>,
ErrorPtr error)>& callback) {
callback.Run(std::move(stream_), nullptr);
}
XmppState state() const { return state_; }
void set_state(XmppState state) { state_ = state; }
void SchedulePing(base::TimeDelta interval,
base::TimeDelta timeout) override {}
void ExpectWritePacketString(base::TimeDelta delta, const std::string& data) {
fake_stream_->ExpectWritePacketString(delta, data);
}
void AddReadPacketString(base::TimeDelta delta, const std::string& data) {
fake_stream_->AddReadPacketString(delta, data);
}
std::unique_ptr<test::FakeStream> stream_;
test::FakeStream* fake_stream_{nullptr};
};
class MockNetwork : public provider::test::MockNetwork {
public:
MockNetwork() {
EXPECT_CALL(*this, AddConnectionChangedCallback(_))
.WillRepeatedly(Return());
}
};
class XmppChannelTest : public ::testing::Test {
protected:
XmppChannelTest() {
EXPECT_CALL(network_, OpenSslSocket("endpoint", 456, _))
.WillOnce(
WithArgs<2>(Invoke(&xmpp_client_, &FakeXmppChannel::Connect)));
}
void StartStream() {
xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
xmpp_client_.AddReadPacketString({}, kStartStreamResponse);
xmpp_client_.Start(nullptr);
RunUntil(XmppChannel::XmppState::kConnected);
}
void StartWithState(XmppChannel::XmppState state) {
StartStream();
xmpp_client_.set_state(state);
}
void RunUntil(XmppChannel::XmppState st) {
for (size_t n = 15; n && xmpp_client_.state() != st; --n)
task_runner_.RunOnce();
EXPECT_EQ(st, xmpp_client_.state());
}
StrictMock<provider::test::FakeTaskRunner> task_runner_;
StrictMock<MockNetwork> network_;
FakeXmppChannel xmpp_client_{&task_runner_, &network_};
};
TEST_F(XmppChannelTest, StartStream) {
EXPECT_EQ(XmppChannel::XmppState::kNotStarted, xmpp_client_.state());
xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
xmpp_client_.Start(nullptr);
RunUntil(XmppChannel::XmppState::kConnected);
}
TEST_F(XmppChannelTest, HandleStartedResponse) {
StartStream();
}
TEST_F(XmppChannelTest, HandleConnected) {
StartWithState(XmppChannel::XmppState::kConnected);
xmpp_client_.AddReadPacketString({}, kConnectedResponse);
xmpp_client_.ExpectWritePacketString({}, kAuthenticationMessage);
RunUntil(XmppChannel::XmppState::kAuthenticationStarted);
}
TEST_F(XmppChannelTest, HandleAuthenticationSucceededResponse) {
StartWithState(XmppChannel::XmppState::kAuthenticationStarted);
xmpp_client_.AddReadPacketString({}, kAuthenticationSucceededResponse);
xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
RunUntil(XmppChannel::XmppState::kStreamRestartedPostAuthentication);
}
TEST_F(XmppChannelTest, HandleAuthenticationFailedResponse) {
StartWithState(XmppChannel::XmppState::kAuthenticationStarted);
xmpp_client_.AddReadPacketString({}, kAuthenticationFailedResponse);
RunUntil(XmppChannel::XmppState::kAuthenticationFailed);
}
TEST_F(XmppChannelTest, HandleStreamRestartedResponse) {
StartWithState(XmppChannel::XmppState::kStreamRestartedPostAuthentication);
xmpp_client_.AddReadPacketString({}, kRestartStreamResponse);
xmpp_client_.ExpectWritePacketString({}, kBindMessage);
RunUntil(XmppChannel::XmppState::kBindSent);
EXPECT_TRUE(xmpp_client_.jid().empty());
xmpp_client_.AddReadPacketString({}, kBindResponse);
xmpp_client_.ExpectWritePacketString({}, kSessionMessage);
RunUntil(XmppChannel::XmppState::kSessionStarted);
EXPECT_EQ(
"110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com"
"/19853128",
xmpp_client_.jid());
xmpp_client_.AddReadPacketString({}, kSessionResponse);
xmpp_client_.ExpectWritePacketString({}, kSubscribeMessage);
RunUntil(XmppChannel::XmppState::kSubscribeStarted);
xmpp_client_.AddReadPacketString({}, kSubscribedResponse);
RunUntil(XmppChannel::XmppState::kSubscribed);
}
} // namespace weave