// Copyright (c) 2011 The Chromium 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 "chrome/browser/ui/webui/sync_internals_ui.h"

#include <cstddef>
#include <string>

#include "base/message_loop.h"
#include "base/values.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_test_util.h"
#include "chrome/browser/sync/profile_sync_service_mock.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/test/profile_mock.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/test_render_view_host.h"
#include "content/browser/tab_contents/test_tab_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using browser_sync::HasArgsAsList;
using browser_sync::JsArgList;
using testing::NiceMock;
using testing::Return;
using testing::StrictMock;

// Subclass of SyncInternalsUI to mock out ExecuteJavascript.
class TestSyncInternalsUI : public SyncInternalsUI {
 public:
  explicit TestSyncInternalsUI(TabContents* contents)
      : SyncInternalsUI(contents) {}
  virtual ~TestSyncInternalsUI() {}

  MOCK_METHOD1(ExecuteJavascript, void(const string16&));
};

class SyncInternalsUITest : public RenderViewHostTestHarness {
 protected:
  // We allocate memory for |sync_internals_ui_| but we don't
  // construct it.  This is because we want to set mock expectations
  // with its address before we construct it, and its constructor
  // calls into our mocks.
  SyncInternalsUITest()
      // The message loop is provided by RenderViewHostTestHarness.
      : ui_thread_(BrowserThread::UI, MessageLoopForUI::current()),
        test_sync_internals_ui_buf_(NULL),
        test_sync_internals_ui_constructor_called_(false) {}

  virtual void SetUp() {
    test_sync_internals_ui_buf_ = operator new(sizeof(TestSyncInternalsUI));
    test_sync_internals_ui_constructor_called_ = false;
    profile_.reset(new NiceMock<ProfileMock>());
    RenderViewHostTestHarness::SetUp();
  }

  virtual void TearDown() {
    if (test_sync_internals_ui_constructor_called_) {
      GetTestSyncInternalsUI()->~TestSyncInternalsUI();
    }
    operator delete(test_sync_internals_ui_buf_);
    RenderViewHostTestHarness::TearDown();
  }

  NiceMock<ProfileMock>* GetProfileMock() {
    return static_cast<NiceMock<ProfileMock>*>(profile());
  }

  // Set up boilerplate expectations for calls done during
  // SyncInternalUI's construction/destruction.
  void ExpectSetupTeardownCalls() {
    EXPECT_CALL(*GetProfileMock(), GetProfileSyncService())
        .WillRepeatedly(Return(&profile_sync_service_mock_));

    EXPECT_CALL(profile_sync_service_mock_, GetJsFrontend())
        .WillRepeatedly(Return(&mock_js_backend_));

    // Called by sync_ui_util::ConstructAboutInformation().
    EXPECT_CALL(profile_sync_service_mock_, HasSyncSetupCompleted())
        .WillRepeatedly(Return(false));

    // Called by SyncInternalsUI's constructor.
    EXPECT_CALL(mock_js_backend_,
                AddHandler(GetTestSyncInternalsUIAddress()));

    // Called by SyncInternalUI's destructor.
    EXPECT_CALL(mock_js_backend_,
                RemoveHandler(GetTestSyncInternalsUIAddress()));
  }

  // Like ExpectSetupTeardownCalls() but with a NULL
  // ProfileSyncService.
  void ExpectSetupTeardownCallsNullService() {
    EXPECT_CALL(*GetProfileMock(), GetProfileSyncService())
        .WillRepeatedly(Return(static_cast<ProfileSyncService*>(NULL)));
  }

  void ConstructTestSyncInternalsUI() {
    if (test_sync_internals_ui_constructor_called_) {
      ADD_FAILURE() << "ConstructTestSyncInternalsUI() should be called "
                    << "at most once per test";
      return;
    }
    new(test_sync_internals_ui_buf_) TestSyncInternalsUI(contents());
    test_sync_internals_ui_constructor_called_ = true;
  }

  TestSyncInternalsUI* GetTestSyncInternalsUI() {
    if (!test_sync_internals_ui_constructor_called_) {
      ADD_FAILURE() << "ConstructTestSyncInternalsUI() should be called "
                    << "before GetTestSyncInternalsUI()";
      return NULL;
    }
    return GetTestSyncInternalsUIAddress();
  }

  // Used for passing into EXPECT_CALL().
  TestSyncInternalsUI* GetTestSyncInternalsUIAddress() {
    EXPECT_TRUE(test_sync_internals_ui_buf_);
    return static_cast<TestSyncInternalsUI*>(test_sync_internals_ui_buf_);
  }

  StrictMock<ProfileSyncServiceMock> profile_sync_service_mock_;
  StrictMock<browser_sync::MockJsFrontend> mock_js_backend_;

 private:
  // Needed by |contents()|.
  BrowserThread ui_thread_;
  void* test_sync_internals_ui_buf_;
  bool test_sync_internals_ui_constructor_called_;
};

TEST_F(SyncInternalsUITest, HandleJsEvent) {
  ExpectSetupTeardownCalls();

  ConstructTestSyncInternalsUI();

  EXPECT_CALL(*GetTestSyncInternalsUI(),
              ExecuteJavascript(ASCIIToUTF16("testMessage(5,true);")));

  ListValue args;
  args.Append(Value::CreateIntegerValue(5));
  args.Append(Value::CreateBooleanValue(true));
  GetTestSyncInternalsUI()->HandleJsEvent("testMessage", JsArgList(args));
}

TEST_F(SyncInternalsUITest, HandleJsEventNullService) {
  ExpectSetupTeardownCallsNullService();

  ConstructTestSyncInternalsUI();

  EXPECT_CALL(*GetTestSyncInternalsUI(),
              ExecuteJavascript(ASCIIToUTF16("testMessage(5,true);")));

  ListValue args;
  args.Append(Value::CreateIntegerValue(5));
  args.Append(Value::CreateBooleanValue(true));
  GetTestSyncInternalsUI()->HandleJsEvent("testMessage", JsArgList(args));
}

TEST_F(SyncInternalsUITest, ProcessWebUIMessageBasic) {
  ExpectSetupTeardownCalls();

  ExtensionHostMsg_DomMessage_Params params;
  params.name = "testName";
  params.arguments.Append(Value::CreateIntegerValue(10));

  EXPECT_CALL(mock_js_backend_,
              ProcessMessage(params.name, HasArgsAsList(params.arguments),
                             GetTestSyncInternalsUIAddress()));

  ConstructTestSyncInternalsUI();

  GetTestSyncInternalsUI()->ProcessWebUIMessage(params);
}

TEST_F(SyncInternalsUITest, ProcessWebUIMessageBasicNullService) {
  ExpectSetupTeardownCallsNullService();

  ConstructTestSyncInternalsUI();

  ExtensionHostMsg_DomMessage_Params params;
  params.name = "testName";
  params.arguments.Append(Value::CreateIntegerValue(5));

  // Should drop the message.
  GetTestSyncInternalsUI()->ProcessWebUIMessage(params);
}

namespace {
const char kAboutInfoCall[] =
    "onGetAboutInfoFinished({\"summary\":\"SYNC DISABLED\"});";
}  // namespace

TEST_F(SyncInternalsUITest, ProcessWebUIMessageGetAboutInfo) {
  ExpectSetupTeardownCalls();

  ExtensionHostMsg_DomMessage_Params params;
  params.name = "getAboutInfo";

  ConstructTestSyncInternalsUI();

  EXPECT_CALL(*GetTestSyncInternalsUI(),
              ExecuteJavascript(ASCIIToUTF16(kAboutInfoCall)));

  GetTestSyncInternalsUI()->ProcessWebUIMessage(params);
}

TEST_F(SyncInternalsUITest, ProcessWebUIMessageGetAboutInfoNullService) {
  ExpectSetupTeardownCallsNullService();

  ExtensionHostMsg_DomMessage_Params params;
  params.name = "getAboutInfo";

  ConstructTestSyncInternalsUI();

  EXPECT_CALL(*GetTestSyncInternalsUI(),
              ExecuteJavascript(ASCIIToUTF16(kAboutInfoCall)));

  GetTestSyncInternalsUI()->ProcessWebUIMessage(params);
}

}  // namespace