1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "jingle/notifier/communicator/single_login_attempt.h"
6
7#include <cstddef>
8
9#include "base/compiler_specific.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "jingle/notifier/base/const_communicator.h"
13#include "jingle/notifier/base/fake_base_task.h"
14#include "jingle/notifier/communicator/login_settings.h"
15#include "net/dns/mock_host_resolver.h"
16#include "net/url_request/url_request_test_util.h"
17#include "talk/xmpp/constants.h"
18#include "talk/xmpp/xmppengine.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
21
22namespace buzz {
23class XmppTaskParentInterface;
24}  // namespace buzz
25
26namespace notifier {
27
28namespace {
29
30enum DelegateState {
31  IDLE, CONNECTED, REDIRECTED, CREDENTIALS_REJECTED, SETTINGS_EXHAUSTED
32};
33
34class FakeDelegate : public SingleLoginAttempt::Delegate {
35 public:
36  FakeDelegate() : state_(IDLE) {}
37
38  virtual void OnConnect(
39      base::WeakPtr<buzz::XmppTaskParentInterface> base_task) OVERRIDE {
40    state_ = CONNECTED;
41    base_task_ = base_task;
42  }
43
44  virtual void OnRedirect(const ServerInformation& redirect_server) OVERRIDE {
45    state_ = REDIRECTED;
46    redirect_server_ = redirect_server;
47  }
48
49  virtual void OnCredentialsRejected() OVERRIDE {
50    state_ = CREDENTIALS_REJECTED;
51  }
52
53  virtual void OnSettingsExhausted() OVERRIDE {
54    state_ = SETTINGS_EXHAUSTED;
55  }
56
57  DelegateState state() const { return state_; }
58
59  base::WeakPtr<buzz::XmppTaskParentInterface> base_task() const {
60    return base_task_;
61  }
62
63  const ServerInformation& redirect_server() const {
64    return redirect_server_;
65  }
66
67 private:
68  DelegateState state_;
69  base::WeakPtr<buzz::XmppTaskParentInterface> base_task_;
70  ServerInformation redirect_server_;
71};
72
73class MyTestURLRequestContext : public net::TestURLRequestContext {
74 public:
75  MyTestURLRequestContext() : TestURLRequestContext(true) {
76    context_storage_.set_host_resolver(
77        scoped_ptr<net::HostResolver>(new net::HangingHostResolver()));
78    Init();
79  }
80  virtual ~MyTestURLRequestContext() {}
81};
82
83class SingleLoginAttemptTest : public ::testing::Test {
84 protected:
85  SingleLoginAttemptTest()
86      : login_settings_(
87          buzz::XmppClientSettings(),
88          new net::TestURLRequestContextGetter(
89              base::MessageLoopProxy::current(),
90              scoped_ptr<net::TestURLRequestContext>(
91                  new MyTestURLRequestContext())),
92          ServerList(
93              1,
94              ServerInformation(
95                  net::HostPortPair("example.com", 100), SUPPORTS_SSLTCP)),
96          false /* try_ssltcp_first */,
97          "auth_mechanism"),
98        attempt_(new SingleLoginAttempt(login_settings_, &fake_delegate_)) {}
99
100  virtual void TearDown() OVERRIDE {
101    message_loop_.RunUntilIdle();
102  }
103
104  void FireRedirect(buzz::XmlElement* redirect_error) {
105    attempt_->OnError(buzz::XmppEngine::ERROR_STREAM, 0, redirect_error);
106  }
107
108  virtual ~SingleLoginAttemptTest() {
109    attempt_.reset();
110    message_loop_.RunUntilIdle();
111  }
112
113 private:
114  base::MessageLoop message_loop_;
115  const LoginSettings login_settings_;
116
117 protected:
118  scoped_ptr<SingleLoginAttempt> attempt_;
119  FakeDelegate fake_delegate_;
120  FakeBaseTask fake_base_task_;
121};
122
123// Fire OnConnect and make sure the base task gets passed to the
124// delegate properly.
125TEST_F(SingleLoginAttemptTest, Basic) {
126  attempt_->OnConnect(fake_base_task_.AsWeakPtr());
127  EXPECT_EQ(CONNECTED, fake_delegate_.state());
128  EXPECT_EQ(fake_base_task_.AsWeakPtr().get(),
129            fake_delegate_.base_task().get());
130}
131
132// Fire OnErrors and make sure the delegate gets the
133// OnSettingsExhausted() event.
134TEST_F(SingleLoginAttemptTest, Error) {
135  for (int i = 0; i < 2; ++i) {
136    EXPECT_EQ(IDLE, fake_delegate_.state());
137    attempt_->OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL);
138  }
139  EXPECT_EQ(SETTINGS_EXHAUSTED, fake_delegate_.state());
140}
141
142// Fire OnErrors but replace the last one with OnConnect, and make
143// sure the delegate still gets the OnConnect message.
144TEST_F(SingleLoginAttemptTest, ErrorThenSuccess) {
145  attempt_->OnError(buzz::XmppEngine::ERROR_NONE, 0, NULL);
146  attempt_->OnConnect(fake_base_task_.AsWeakPtr());
147  EXPECT_EQ(CONNECTED, fake_delegate_.state());
148  EXPECT_EQ(fake_base_task_.AsWeakPtr().get(),
149            fake_delegate_.base_task().get());
150}
151
152buzz::XmlElement* MakeRedirectError(const std::string& redirect_server) {
153  buzz::XmlElement* stream_error =
154      new buzz::XmlElement(buzz::QN_STREAM_ERROR, true);
155  stream_error->AddElement(
156      new buzz::XmlElement(buzz::QN_XSTREAM_SEE_OTHER_HOST, true));
157  buzz::XmlElement* text =
158      new buzz::XmlElement(buzz::QN_XSTREAM_TEXT, true);
159  stream_error->AddElement(text);
160  text->SetBodyText(redirect_server);
161  return stream_error;
162}
163
164// Fire a redirect and make sure the delegate gets the proper redirect
165// server info.
166TEST_F(SingleLoginAttemptTest, Redirect) {
167  const ServerInformation redirect_server(
168      net::HostPortPair("example.com", 1000),
169      SUPPORTS_SSLTCP);
170
171  scoped_ptr<buzz::XmlElement> redirect_error(
172      MakeRedirectError(redirect_server.server.ToString()));
173  FireRedirect(redirect_error.get());
174
175  EXPECT_EQ(REDIRECTED, fake_delegate_.state());
176  EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server));
177}
178
179// Fire a redirect with the host only and make sure the delegate gets
180// the proper redirect server info with the default XMPP port.
181TEST_F(SingleLoginAttemptTest, RedirectHostOnly) {
182  const ServerInformation redirect_server(
183      net::HostPortPair("example.com", kDefaultXmppPort),
184      SUPPORTS_SSLTCP);
185
186  scoped_ptr<buzz::XmlElement> redirect_error(
187      MakeRedirectError(redirect_server.server.host()));
188  FireRedirect(redirect_error.get());
189
190  EXPECT_EQ(REDIRECTED, fake_delegate_.state());
191  EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server));
192}
193
194// Fire a redirect with a zero port and make sure the delegate gets
195// the proper redirect server info with the default XMPP port.
196TEST_F(SingleLoginAttemptTest, RedirectZeroPort) {
197  const ServerInformation redirect_server(
198      net::HostPortPair("example.com", kDefaultXmppPort),
199      SUPPORTS_SSLTCP);
200
201  scoped_ptr<buzz::XmlElement> redirect_error(
202      MakeRedirectError(redirect_server.server.host() + ":0"));
203  FireRedirect(redirect_error.get());
204
205  EXPECT_EQ(REDIRECTED, fake_delegate_.state());
206  EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server));
207}
208
209// Fire a redirect with an invalid port and make sure the delegate
210// gets the proper redirect server info with the default XMPP port.
211TEST_F(SingleLoginAttemptTest, RedirectInvalidPort) {
212  const ServerInformation redirect_server(
213      net::HostPortPair("example.com", kDefaultXmppPort),
214      SUPPORTS_SSLTCP);
215
216  scoped_ptr<buzz::XmlElement> redirect_error(
217      MakeRedirectError(redirect_server.server.host() + ":invalidport"));
218  FireRedirect(redirect_error.get());
219
220  EXPECT_EQ(REDIRECTED, fake_delegate_.state());
221  EXPECT_TRUE(fake_delegate_.redirect_server().Equals(redirect_server));
222}
223
224// Fire an empty redirect and make sure the delegate does not get a
225// redirect.
226TEST_F(SingleLoginAttemptTest, RedirectEmpty) {
227  scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string()));
228  FireRedirect(redirect_error.get());
229  EXPECT_EQ(IDLE, fake_delegate_.state());
230}
231
232// Fire a redirect with a missing text element and make sure the
233// delegate does not get a redirect.
234TEST_F(SingleLoginAttemptTest, RedirectMissingText) {
235  scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string()));
236  redirect_error->RemoveChildAfter(redirect_error->FirstChild());
237  FireRedirect(redirect_error.get());
238  EXPECT_EQ(IDLE, fake_delegate_.state());
239}
240
241// Fire a redirect with a missing see-other-host element and make sure
242// the delegate does not get a redirect.
243TEST_F(SingleLoginAttemptTest, RedirectMissingSeeOtherHost) {
244  scoped_ptr<buzz::XmlElement> redirect_error(MakeRedirectError(std::string()));
245  redirect_error->RemoveChildAfter(NULL);
246  FireRedirect(redirect_error.get());
247  EXPECT_EQ(IDLE, fake_delegate_.state());
248}
249
250// Fire 'Unauthorized' errors and make sure the delegate gets the
251// OnCredentialsRejected() event.
252TEST_F(SingleLoginAttemptTest, CredentialsRejected) {
253  attempt_->OnError(buzz::XmppEngine::ERROR_UNAUTHORIZED, 0, NULL);
254  EXPECT_EQ(CREDENTIALS_REJECTED, fake_delegate_.state());
255}
256
257}  // namespace
258
259}  // namespace notifier
260