1// Copyright 2014 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 "chrome/browser/sync/sync_error_notifier_ash.h"
6
7#include "ash/test/ash_test_base.h"
8#include "base/memory/scoped_ptr.h"
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/notifications/notification.h"
11#include "chrome/browser/notifications/notification_ui_manager.h"
12#include "chrome/browser/sync/profile_sync_service_mock.h"
13#include "chrome/browser/sync/sync_error_controller.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/webui/signin/login_ui_service.h"
16#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/test/base/testing_browser_process.h"
19#include "chrome/test/base/testing_profile.h"
20#include "chrome/test/base/testing_profile_manager.h"
21#include "testing/gmock/include/gmock/gmock.h"
22#include "testing/gtest/include/gtest/gtest.h"
23#include "ui/message_center/notification.h"
24
25#if defined(OS_WIN)
26#include "chrome/browser/ui/ash/ash_util.h"
27#include "ui/aura/test/test_screen.h"
28#include "ui/gfx/screen.h"
29#include "ui/gfx/screen_type_delegate.h"
30#endif
31
32using ::testing::NiceMock;
33using ::testing::Return;
34using ::testing::ReturnRef;
35using ::testing::_;
36
37namespace ash {
38namespace test {
39
40namespace {
41
42static const char kTestAccountId[] = "testuser@test.com";
43
44// Notification ID corresponding to kProfileSyncNotificationId + kTestAccountId.
45static const std::string kNotificationId =
46    "chrome://settings/sync/testuser@test.com";
47
48#if defined(OS_WIN)
49class ScreenTypeDelegateDesktop : public gfx::ScreenTypeDelegate {
50 public:
51  ScreenTypeDelegateDesktop() {}
52  virtual ~ScreenTypeDelegateDesktop() {}
53  virtual gfx::ScreenType GetScreenTypeForNativeView(
54      gfx::NativeView view) OVERRIDE {
55    return chrome::IsNativeViewInAsh(view) ?
56        gfx::SCREEN_TYPE_ALTERNATE :
57        gfx::SCREEN_TYPE_NATIVE;
58  }
59
60 private:
61  DISALLOW_COPY_AND_ASSIGN(ScreenTypeDelegateDesktop);
62};
63#endif
64
65class FakeLoginUIService: public LoginUIService {
66 public:
67  FakeLoginUIService() : LoginUIService(NULL) {}
68  virtual ~FakeLoginUIService() {}
69};
70
71class FakeLoginUI : public LoginUIService::LoginUI {
72 public:
73  FakeLoginUI() : focus_ui_call_count_(0) {}
74
75  virtual ~FakeLoginUI() {}
76
77  int focus_ui_call_count() const { return focus_ui_call_count_; }
78
79 private:
80  // LoginUIService::LoginUI:
81  virtual void FocusUI() OVERRIDE {
82    ++focus_ui_call_count_;
83  }
84  virtual void CloseUI() OVERRIDE {}
85
86  int focus_ui_call_count_;
87};
88
89KeyedService* BuildMockLoginUIService(
90    content::BrowserContext* profile) {
91  return new FakeLoginUIService();
92}
93
94class SyncErrorNotifierTest : public AshTestBase  {
95 public:
96  SyncErrorNotifierTest() {}
97  virtual ~SyncErrorNotifierTest() {}
98
99  virtual void SetUp() OVERRIDE {
100    profile_manager_.reset(
101        new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
102    ASSERT_TRUE(profile_manager_->SetUp());
103
104    profile_ = profile_manager_->CreateTestingProfile(kTestAccountId);
105
106    TestingBrowserProcess::GetGlobal();
107    AshTestBase::SetUp();
108
109    // Set up a desktop screen for Windows to hold native widgets, used when
110    // adding desktop widgets (i.e., message center notifications).
111#if defined(OS_WIN)
112    test_screen_.reset(aura::TestScreen::Create(gfx::Size()));
113    gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen_.get());
114    gfx::Screen::SetScreenTypeDelegate(new ScreenTypeDelegateDesktop);
115#endif
116
117    service_.reset(new NiceMock<ProfileSyncServiceMock>(profile_));
118
119    FakeLoginUIService* login_ui_service = static_cast<FakeLoginUIService*>(
120        LoginUIServiceFactory::GetInstance()->SetTestingFactoryAndUse(
121            profile_, BuildMockLoginUIService));
122    login_ui_service->SetLoginUI(&login_ui_);
123
124    error_controller_.reset(new SyncErrorController(service_.get()));
125    error_notifier_.reset(new SyncErrorNotifier(error_controller_.get(),
126                                                profile_));
127
128    notification_ui_manager_ = g_browser_process->notification_ui_manager();
129  }
130
131  virtual void TearDown() OVERRIDE {
132    error_notifier_->Shutdown();
133    service_.reset();
134#if defined(OS_WIN)
135    test_screen_.reset();
136#endif
137    profile_manager_.reset();
138
139    AshTestBase::TearDown();
140  }
141
142 protected:
143  // Utility function to test that SyncErrorNotifier behaves correctly for the
144  // given error condition.
145  void VerifySyncErrorNotifierResult(GoogleServiceAuthError::State error_state,
146                                     bool is_signed_in,
147                                     bool is_error) {
148    EXPECT_CALL(*service_, HasSyncSetupCompleted())
149                .WillRepeatedly(Return(is_signed_in));
150
151    GoogleServiceAuthError auth_error(error_state);
152    EXPECT_CALL(*service_, GetAuthError()).WillRepeatedly(
153        ReturnRef(auth_error));
154
155    error_controller_->OnStateChanged();
156    EXPECT_EQ(is_error, error_controller_->HasError());
157
158    // If there is an error we should see a notification.
159    const Notification* notification = notification_ui_manager_->
160        FindById(kNotificationId);
161    if (is_error) {
162      ASSERT_TRUE(notification);
163      ASSERT_FALSE(notification->title().empty());
164      ASSERT_FALSE(notification->title().empty());
165      ASSERT_EQ((size_t)1, notification->buttons().size());
166    } else {
167      ASSERT_FALSE(notification);
168    }
169  }
170
171#if defined(OS_WIN)
172  scoped_ptr<gfx::Screen> test_screen_;
173#endif
174  scoped_ptr<TestingProfileManager> profile_manager_;
175  scoped_ptr<SyncErrorController> error_controller_;
176  scoped_ptr<SyncErrorNotifier> error_notifier_;
177  scoped_ptr<NiceMock<ProfileSyncServiceMock> > service_;
178  TestingProfile* profile_;
179  FakeLoginUI login_ui_;
180  NotificationUIManager* notification_ui_manager_;
181
182  DISALLOW_COPY_AND_ASSIGN(SyncErrorNotifierTest);
183};
184
185} // namespace
186
187// Test that SyncErrorNotifier shows an notification if a passphrase is
188// required.
189// Disabled on Windows: http://crbug.com/373238
190#if defined(OS_WIN)
191#define MAYBE_PassphraseNotification DISABLED_PassphraseNotification
192#else
193#define MAYBE_PassphraseNotification PassphraseNotification
194#endif
195TEST_F(SyncErrorNotifierTest, MAYBE_PassphraseNotification) {
196  ASSERT_FALSE(notification_ui_manager_->FindById(kNotificationId));
197
198  browser_sync::SyncBackendHost::Status status;
199  EXPECT_CALL(*service_, QueryDetailedSyncStatus(_))
200              .WillRepeatedly(Return(false));
201
202  EXPECT_CALL(*service_, IsPassphraseRequired())
203              .WillRepeatedly(Return(true));
204  EXPECT_CALL(*service_, IsPassphraseRequiredForDecryption())
205              .WillRepeatedly(Return(true));
206  {
207    SCOPED_TRACE("Expected a notification for passphrase error");
208    VerifySyncErrorNotifierResult(GoogleServiceAuthError::NONE,
209                                  true /* signed in */,
210                                  true /* error */);
211  }
212
213  // Check that no notification is shown if there is no error.
214  EXPECT_CALL(*service_, IsPassphraseRequired())
215              .WillRepeatedly(Return(false));
216  EXPECT_CALL(*service_, IsPassphraseRequiredForDecryption())
217              .WillRepeatedly(Return(false));
218  {
219    SCOPED_TRACE("Not expecting notification since no error exists");
220    VerifySyncErrorNotifierResult(GoogleServiceAuthError::NONE,
221                                  true /* signed in */,
222                                  false /* no error */);
223  }
224
225  // Check that no notification is shown if sync setup is not completed.
226  EXPECT_CALL(*service_, IsPassphraseRequired())
227              .WillRepeatedly(Return(true));
228  EXPECT_CALL(*service_, IsPassphraseRequiredForDecryption())
229              .WillRepeatedly(Return(true));
230  {
231    SCOPED_TRACE("Not expecting notification since sync setup is incomplete");
232    VerifySyncErrorNotifierResult(
233        GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
234        false /* not signed in */,
235        false /* no error */);
236  }
237}
238
239}  // namespace test
240}  // namespace ash
241