普通文本  |  259行  |  7.72 KB

// 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 <string>

#include "base/message_loop.h"
#include "chrome/browser/sync/notifier/chrome_invalidation_client.h"
#include "chrome/browser/sync/notifier/state_writer.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/syncable/model_type_payload_map.h"
#include "jingle/notifier/base/fake_base_task.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sync_notifier {

using ::testing::_;
using ::testing::Return;
using ::testing::StrictMock;

namespace {

const char kClientId[] = "client_id";
const char kClientInfo[] = "client_info";
const char kState[] = "state";

static const int64 kUnknownVersion =
    invalidation::InvalidationListener::UNKNOWN_OBJECT_VERSION;

class MockListener : public ChromeInvalidationClient::Listener {
 public:
  MOCK_METHOD1(OnInvalidate, void(const syncable::ModelTypePayloadMap&));
  MOCK_METHOD1(OnSessionStatusChanged, void(bool));
};

class MockStateWriter : public StateWriter {
 public:
  MOCK_METHOD1(WriteState, void(const std::string&));
};

class MockCallback {
 public:
  MOCK_METHOD0(Run, void());

  invalidation::Closure* MakeClosure() {
    return invalidation::NewPermanentCallback(this, &MockCallback::Run);
  }
};

}  // namespace

class ChromeInvalidationClientTest : public testing::Test {
 protected:
  virtual void SetUp() {
    client_.Start(kClientId, kClientInfo, kState,
                  &mock_listener_, &mock_state_writer_,
                  fake_base_task_.AsWeakPtr());
  }

  virtual void TearDown() {
    client_.Stop();
    message_loop_.RunAllPending();
  }

  // Simulates DoInformOutboundListener() from network-manager.cc.
  void SimulateInformOutboundListener() {
    // Explicitness hack here to work around broken callback
    // implementations.
    void (invalidation::NetworkCallback::*run_function)(
        invalidation::NetworkEndpoint* const&) =
        &invalidation::NetworkCallback::Run;

    client_.chrome_system_resources_.ScheduleOnListenerThread(
        invalidation::NewPermanentCallback(
            client_.handle_outbound_packet_callback_.get(), run_function,
            client_.invalidation_client_->network_endpoint()));
  }

  // |payload| can be NULL, but not |type_name|.
  void FireInvalidate(const char* type_name,
                      int64 version, const char* payload) {
    const invalidation::ObjectId object_id(
        invalidation::ObjectSource::CHROME_SYNC, type_name);
    std::string payload_tmp = payload ? payload : "";
    const invalidation::Invalidation invalidation(
        object_id, version, payload ? &payload_tmp : NULL, NULL);
    MockCallback mock_callback;
    EXPECT_CALL(mock_callback, Run());
    client_.Invalidate(invalidation, mock_callback.MakeClosure());
  }

  void FireInvalidateAll() {
    MockCallback mock_callback;
    EXPECT_CALL(mock_callback, Run());
    client_.InvalidateAll(mock_callback.MakeClosure());
  }

  MessageLoop message_loop_;
  StrictMock<MockListener> mock_listener_;
  StrictMock<MockStateWriter> mock_state_writer_;
  notifier::FakeBaseTask fake_base_task_;
  ChromeInvalidationClient client_;
};

namespace {

syncable::ModelTypePayloadMap MakeMap(syncable::ModelType model_type,
                                      const std::string& payload) {
  syncable::ModelTypePayloadMap type_payloads;
  type_payloads[model_type] = payload;
  return type_payloads;
}

syncable::ModelTypePayloadMap MakeMapFromSet(syncable::ModelTypeSet types,
                                             const std::string& payload) {
  syncable::ModelTypePayloadMap type_payloads;
  for (syncable::ModelTypeSet::const_iterator it = types.begin();
       it != types.end(); ++it) {
    type_payloads[*it] = payload;
  }
  return type_payloads;
}

}  // namespace

TEST_F(ChromeInvalidationClientTest, InvalidateBadObjectId) {
  syncable::ModelTypeSet types;
  types.insert(syncable::BOOKMARKS);
  types.insert(syncable::APPS);
  client_.RegisterTypes(types);
  EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, "")));
  FireInvalidate("bad", 1, NULL);
}

TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) {
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::BOOKMARKS, "")));
  FireInvalidate("BOOKMARK", 1, NULL);
}

TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) {
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::PREFERENCES, "payload")));
  FireInvalidate("PREFERENCE", 1, "payload");
}

TEST_F(ChromeInvalidationClientTest, InvalidateVersion) {
  using ::testing::Mock;

  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::APPS, "")));

  // Should trigger.
  FireInvalidate("APP", 1, NULL);

  Mock::VerifyAndClearExpectations(&mock_listener_);

  // Should be dropped.
  FireInvalidate("APP", 1, NULL);
}

TEST_F(ChromeInvalidationClientTest, InvalidateUnknownVersion) {
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::EXTENSIONS, "")))
      .Times(2);

  // Should trigger twice.
  FireInvalidate("EXTENSION", kUnknownVersion, NULL);
  FireInvalidate("EXTENSION", kUnknownVersion, NULL);
}

TEST_F(ChromeInvalidationClientTest, InvalidateVersionMultipleTypes) {
  using ::testing::Mock;

  syncable::ModelTypeSet types;
  types.insert(syncable::BOOKMARKS);
  types.insert(syncable::APPS);
  client_.RegisterTypes(types);

  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::APPS, "")));
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::EXTENSIONS, "")));

  // Should trigger both.
  FireInvalidate("APP", 3, NULL);
  FireInvalidate("EXTENSION", 2, NULL);

  Mock::VerifyAndClearExpectations(&mock_listener_);

  // Should both be dropped.
  FireInvalidate("APP", 1, NULL);
  FireInvalidate("EXTENSION", 1, NULL);

  Mock::VerifyAndClearExpectations(&mock_listener_);

  // InvalidateAll shouldn't change any version state.
  EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, "")));
  FireInvalidateAll();

  Mock::VerifyAndClearExpectations(&mock_listener_);

  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::PREFERENCES, "")));
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::EXTENSIONS, "")));
  EXPECT_CALL(mock_listener_,
              OnInvalidate(MakeMap(syncable::APPS, "")));

  // Should trigger all three.
  FireInvalidate("PREFERENCE", 5, NULL);
  FireInvalidate("EXTENSION", 3, NULL);
  FireInvalidate("APP", 4, NULL);
}

TEST_F(ChromeInvalidationClientTest, InvalidateAll) {
  syncable::ModelTypeSet types;
  types.insert(syncable::PREFERENCES);
  types.insert(syncable::EXTENSIONS);
  client_.RegisterTypes(types);
  EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, "")));
  FireInvalidateAll();
}

TEST_F(ChromeInvalidationClientTest, RegisterTypes) {
  syncable::ModelTypeSet types;
  types.insert(syncable::PREFERENCES);
  types.insert(syncable::EXTENSIONS);
  client_.RegisterTypes(types);
  // Registered types should be preserved across Stop/Start.
  TearDown();
  SetUp();
  EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, "")));
  FireInvalidateAll();
}

// Outbound packet sending should be resilient to
// changing/disappearing base tasks.
TEST_F(ChromeInvalidationClientTest, OutboundPackets) {
  SimulateInformOutboundListener();

  notifier::FakeBaseTask fake_base_task;
  client_.ChangeBaseTask(fake_base_task.AsWeakPtr());

  SimulateInformOutboundListener();

  {
    notifier::FakeBaseTask fake_base_task2;
    client_.ChangeBaseTask(fake_base_task2.AsWeakPtr());
  }

  SimulateInformOutboundListener();
}

// TODO(akalin): Flesh out unit tests.

}  // namespace sync_notifier