root/sync/notifier/sync_invalidation_listener_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetRegisteredIds
  2. ClearAckedHandles
  3. IsAckedHandle
  4. Start
  5. Stop
  6. Register
  7. Register
  8. Unregister
  9. Unregister
  10. Acknowledge
  11. drop_handlers_deleter_
  12. GetInvalidationCount
  13. GetVersion
  14. GetPayload
  15. IsUnknownVersion
  16. StartsWithUnknownVersion
  17. GetInvalidatorState
  18. GetDropTrackerForObject
  19. AcknowledgeNthInvalidation
  20. AcknowledgeAll
  21. DropNthInvalidation
  22. RecoverFromDropEvent
  23. OnInvalidate
  24. OnInvalidatorStateChange
  25. CreateFakeInvalidationClient
  26. fake_delegate_
  27. SetUp
  28. TearDown
  29. RestartClient
  30. StartClient
  31. StopClient
  32. GetInvalidationCount
  33. GetVersion
  34. GetPayload
  35. IsUnknownVersion
  36. StartsWithUnknownVersion
  37. AcknowledgeNthInvalidation
  38. DropNthInvalidation
  39. RecoverFromDropEvent
  40. AcknowledgeAll
  41. GetInvalidatorState
  42. GetInvalidatorClientId
  43. GetBootstrapData
  44. GetSavedInvalidations
  45. GetSavedInvalidationsForType
  46. GetRegisteredIds
  47. FireInvalidate
  48. FireInvalidateUnknownVersion
  49. FireInvalidateAll
  50. WriteState
  51. FlushPendingWrites
  52. EnableNotifications
  53. DisableNotifications
  54. TEST_F
  55. TEST_F
  56. TEST_F
  57. TEST_F
  58. TEST_F
  59. TEST_F
  60. TEST_F
  61. TEST_F
  62. TEST_F
  63. TEST_F
  64. TEST_F
  65. TEST_F
  66. TEST_F
  67. TEST_F
  68. TEST_F
  69. TEST_F
  70. TEST_F
  71. TEST_F
  72. TEST_F
  73. TEST_F
  74. TEST_F
  75. TEST_F
  76. TEST_F
  77. TEST_F
  78. TEST_F
  79. TEST_F
  80. TEST_F
  81. TEST_F
  82. TEST_F
  83. SetUp
  84. TEST_F

// Copyright (c) 2012 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 <cstddef>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "google/cacheinvalidation/include/invalidation-client.h"
#include "google/cacheinvalidation/include/types.h"
#include "jingle/notifier/listener/fake_push_client.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/notifier/dropped_invalidation_tracker.h"
#include "sync/notifier/fake_invalidation_state_tracker.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/object_id_invalidation_map.h"
#include "sync/notifier/push_client_channel.h"
#include "sync/notifier/sync_invalidation_listener.h"
#include "sync/notifier/unacked_invalidation_set_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

namespace {

using invalidation::AckHandle;
using invalidation::ObjectId;

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

const char kState[] = "state";
const char kNewState[] = "new_state";

const char kPayload1[] = "payload1";
const char kPayload2[] = "payload2";

const int64 kVersion1 = 1LL;
const int64 kVersion2 = 2LL;

const int kChromeSyncSourceId = 1004;

struct AckHandleLessThan {
  bool operator()(const AckHandle& lhs, const AckHandle& rhs) const {
    return lhs.handle_data() < rhs.handle_data();
  }
};

typedef std::set<AckHandle, AckHandleLessThan> AckHandleSet;

// Fake invalidation::InvalidationClient implementation that keeps
// track of registered IDs and acked handles.
class FakeInvalidationClient : public invalidation::InvalidationClient {
 public:
  FakeInvalidationClient() : started_(false) {}
  virtual ~FakeInvalidationClient() {}

  const ObjectIdSet& GetRegisteredIds() const {
    return registered_ids_;
  }

  void ClearAckedHandles() {
    acked_handles_.clear();
  }

  bool IsAckedHandle(const AckHandle& ack_handle) const {
    return (acked_handles_.find(ack_handle) != acked_handles_.end());
  }

  // invalidation::InvalidationClient implementation.

  virtual void Start() OVERRIDE {
    started_ = true;
  }

  virtual void Stop() OVERRIDE {
    started_ = false;
  }

  virtual void Register(const ObjectId& object_id) OVERRIDE {
    if (!started_) {
      ADD_FAILURE();
      return;
    }
    registered_ids_.insert(object_id);
  }

  virtual void Register(
      const invalidation::vector<ObjectId>& object_ids) OVERRIDE {
    if (!started_) {
      ADD_FAILURE();
      return;
    }
    registered_ids_.insert(object_ids.begin(), object_ids.end());
  }

  virtual void Unregister(const ObjectId& object_id) OVERRIDE {
    if (!started_) {
      ADD_FAILURE();
      return;
    }
    registered_ids_.erase(object_id);
  }

  virtual void Unregister(
      const invalidation::vector<ObjectId>& object_ids) OVERRIDE {
    if (!started_) {
      ADD_FAILURE();
      return;
    }
    for (invalidation::vector<ObjectId>::const_iterator
             it = object_ids.begin(); it != object_ids.end(); ++it) {
      registered_ids_.erase(*it);
    }
  }

  virtual void Acknowledge(const AckHandle& ack_handle) OVERRIDE {
    if (!started_) {
      ADD_FAILURE();
      return;
    }
    acked_handles_.insert(ack_handle);
  }

 private:
  bool started_;
  ObjectIdSet registered_ids_;
  AckHandleSet acked_handles_;
};

// Fake delegate tkat keeps track of invalidation counts, payloads,
// and state.
class FakeDelegate : public SyncInvalidationListener::Delegate {
 public:
  explicit FakeDelegate(SyncInvalidationListener* listener)
      : state_(TRANSIENT_INVALIDATION_ERROR),
        drop_handlers_deleter_(&drop_handlers_) {}
  virtual ~FakeDelegate() {}

  size_t GetInvalidationCount(const ObjectId& id) const {
    Map::const_iterator it = invalidations_.find(id);
    if (it == invalidations_.end()) {
      return 0;
    } else {
      return it->second.size();
    }
  }

  int64 GetVersion(const ObjectId& id) const {
    Map::const_iterator it = invalidations_.find(id);
    if (it == invalidations_.end()) {
      ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
      return 0;
    } else {
      return it->second.back().version();
    }
  }

  std::string GetPayload(const ObjectId& id) const {
    Map::const_iterator it = invalidations_.find(id);
    if (it == invalidations_.end()) {
      ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
      return 0;
    } else {
      return it->second.back().payload();
    }
  }

  bool IsUnknownVersion(const ObjectId& id) const {
    Map::const_iterator it = invalidations_.find(id);
    if (it == invalidations_.end()) {
      ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
      return false;
    } else {
      return it->second.back().is_unknown_version();
    }
  }

  bool StartsWithUnknownVersion(const ObjectId& id) const {
    Map::const_iterator it = invalidations_.find(id);
    if (it == invalidations_.end()) {
      ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id);
      return false;
    } else {
      return it->second.front().is_unknown_version();
    }
  }

  InvalidatorState GetInvalidatorState() const {
    return state_;
  }

  DroppedInvalidationTracker* GetDropTrackerForObject(const ObjectId& id) {
    DropHandlers::iterator it = drop_handlers_.find(id);
    if (it == drop_handlers_.end()) {
      drop_handlers_.insert(
          std::make_pair(id, new DroppedInvalidationTracker(id)));
      return drop_handlers_.find(id)->second;
    } else {
      return it->second;
    }
  }

  void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) {
    List& list = invalidations_[id];
    List::iterator it = list.begin() + n;
    it->Acknowledge();
  }

  void AcknowledgeAll(const ObjectId& id) {
    List& list = invalidations_[id];
    for (List::iterator it = list.begin(); it != list.end(); ++it) {
      it->Acknowledge();
    }
  }

  void DropNthInvalidation(const ObjectId& id, size_t n) {
    DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id);
    List& list = invalidations_[id];
    List::iterator it = list.begin() + n;
    it->Drop(drop_tracker);
  }

  void RecoverFromDropEvent(const ObjectId& id) {
    DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id);
    drop_tracker->RecordRecoveryFromDropEvent();
  }

  // SyncInvalidationListener::Delegate implementation.
  virtual void OnInvalidate(
      const ObjectIdInvalidationMap& invalidation_map) OVERRIDE {
    ObjectIdSet ids = invalidation_map.GetObjectIds();
    for (ObjectIdSet::iterator it = ids.begin(); it != ids.end(); ++it) {
      const SingleObjectInvalidationSet& incoming =
          invalidation_map.ForObject(*it);
      List& list = invalidations_[*it];
      list.insert(list.end(), incoming.begin(), incoming.end());
    }
  }

  virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE {
    state_ = state;
  }

 private:
  typedef std::vector<Invalidation> List;
  typedef std::map<ObjectId, List, ObjectIdLessThan> Map;
  typedef std::map<ObjectId,
                   DroppedInvalidationTracker*,
                   ObjectIdLessThan> DropHandlers;

  Map invalidations_;
  InvalidatorState state_;
  DropHandlers drop_handlers_;
  STLValueDeleter<DropHandlers> drop_handlers_deleter_;
};

invalidation::InvalidationClient* CreateFakeInvalidationClient(
    FakeInvalidationClient** fake_invalidation_client,
    invalidation::SystemResources* resources,
    int client_type,
    const invalidation::string& client_name,
    const invalidation::string& application_name,
    invalidation::InvalidationListener* listener) {
  *fake_invalidation_client = new FakeInvalidationClient();
  return *fake_invalidation_client;
}

class SyncInvalidationListenerTest : public testing::Test {
 protected:
  SyncInvalidationListenerTest()
      : kBookmarksId_(kChromeSyncSourceId, "BOOKMARK"),
        kPreferencesId_(kChromeSyncSourceId, "PREFERENCE"),
        kExtensionsId_(kChromeSyncSourceId, "EXTENSION"),
        kAppsId_(kChromeSyncSourceId, "APP"),
        fake_push_client_(new notifier::FakePushClient()),
        fake_invalidation_client_(NULL),
        listener_(scoped_ptr<SyncNetworkChannel>(new PushClientChannel(
            scoped_ptr<notifier::PushClient>(fake_push_client_)))),
        fake_delegate_(&listener_) {}

  virtual void SetUp() {
    StartClient();

    registered_ids_.insert(kBookmarksId_);
    registered_ids_.insert(kPreferencesId_);
    listener_.UpdateRegisteredIds(registered_ids_);
  }

  virtual void TearDown() {
    StopClient();
  }

  // Restart client without re-registering IDs.
  void RestartClient() {
    StopClient();
    StartClient();
  }

  void StartClient() {
    fake_invalidation_client_ = NULL;
    listener_.Start(base::Bind(&CreateFakeInvalidationClient,
                               &fake_invalidation_client_),
                    kClientId, kClientInfo, kState,
                    fake_tracker_.GetSavedInvalidations(),
                    MakeWeakHandle(fake_tracker_.AsWeakPtr()),
                    &fake_delegate_);
    DCHECK(fake_invalidation_client_);
  }

  void StopClient() {
    // listener_.StopForTest() stops the invalidation scheduler, which
    // deletes any pending tasks without running them.  Some tasks
    // "run and delete" another task, so they must be run in order to
    // avoid leaking the inner task.  listener_.StopForTest() does not
    // schedule any tasks, so it's both necessary and sufficient to
    // drain the task queue before calling it.
    FlushPendingWrites();
    fake_invalidation_client_ = NULL;
    listener_.StopForTest();
  }

  size_t GetInvalidationCount(const ObjectId& id) const {
    return fake_delegate_.GetInvalidationCount(id);
  }

  int64 GetVersion(const ObjectId& id) const {
    return fake_delegate_.GetVersion(id);
  }

  std::string GetPayload(const ObjectId& id) const {
    return fake_delegate_.GetPayload(id);
  }

  bool IsUnknownVersion(const ObjectId& id) const {
    return fake_delegate_.IsUnknownVersion(id);
  }

  bool StartsWithUnknownVersion(const ObjectId& id) const {
    return fake_delegate_.StartsWithUnknownVersion(id);
  }

  void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) {
    fake_delegate_.AcknowledgeNthInvalidation(id, n);
  }

  void DropNthInvalidation(const ObjectId& id, size_t n) {
    return fake_delegate_.DropNthInvalidation(id, n);
  }

  void RecoverFromDropEvent(const ObjectId& id) {
    return fake_delegate_.RecoverFromDropEvent(id);
  }

  void AcknowledgeAll(const ObjectId& id) {
    fake_delegate_.AcknowledgeAll(id);
  }

  InvalidatorState GetInvalidatorState() const {
    return fake_delegate_.GetInvalidatorState();
  }

  std::string GetInvalidatorClientId() const {
    return fake_tracker_.GetInvalidatorClientId();
  }

  std::string GetBootstrapData() const {
    return fake_tracker_.GetBootstrapData();
  }

  UnackedInvalidationsMap GetSavedInvalidations() {
    // Allow any queued writes to go through first.
    FlushPendingWrites();
    return fake_tracker_.GetSavedInvalidations();
  }

  SingleObjectInvalidationSet GetSavedInvalidationsForType(const ObjectId& id) {
    const UnackedInvalidationsMap& saved_state = GetSavedInvalidations();
    UnackedInvalidationsMap::const_iterator it =
        saved_state.find(kBookmarksId_);
    if (it == saved_state.end()) {
      ADD_FAILURE() << "No state saved for ID " << ObjectIdToString(id);
      return SingleObjectInvalidationSet();
    }
    ObjectIdInvalidationMap map;
    it->second.ExportInvalidations(WeakHandle<AckHandler>(), &map);
    if (map.Empty()) {
      return SingleObjectInvalidationSet();
    } else  {
      return map.ForObject(id);
    }
  }

  ObjectIdSet GetRegisteredIds() const {
    return fake_invalidation_client_->GetRegisteredIds();
  }

  // |payload| can be NULL.
  void FireInvalidate(const ObjectId& object_id,
                      int64 version, const char* payload) {
    invalidation::Invalidation inv;
    if (payload) {
      inv = invalidation::Invalidation(object_id, version, payload);
    } else {
      inv = invalidation::Invalidation(object_id, version);
    }
    const AckHandle ack_handle("fakedata");
    fake_invalidation_client_->ClearAckedHandles();
    listener_.Invalidate(fake_invalidation_client_, inv, ack_handle);
    EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
  }

  // |payload| can be NULL, but not |type_name|.
  void FireInvalidateUnknownVersion(const ObjectId& object_id) {
    const AckHandle ack_handle("fakedata_unknown");
    fake_invalidation_client_->ClearAckedHandles();
    listener_.InvalidateUnknownVersion(fake_invalidation_client_,
                                       object_id,
                                       ack_handle);
    EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
  }

  void FireInvalidateAll() {
    const AckHandle ack_handle("fakedata_all");
    fake_invalidation_client_->ClearAckedHandles();
    listener_.InvalidateAll(fake_invalidation_client_, ack_handle);
    EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle));
  }

  void WriteState(const std::string& new_state) {
    listener_.WriteState(new_state);

    // Pump message loop to trigger
    // InvalidationStateTracker::WriteState().
    FlushPendingWrites();
  }

  void FlushPendingWrites() {
    message_loop_.RunUntilIdle();
  }

  void EnableNotifications() {
    fake_push_client_->EnableNotifications();
  }

  void DisableNotifications(notifier::NotificationsDisabledReason reason) {
    fake_push_client_->DisableNotifications(reason);
  }

  const ObjectId kBookmarksId_;
  const ObjectId kPreferencesId_;
  const ObjectId kExtensionsId_;
  const ObjectId kAppsId_;

  ObjectIdSet registered_ids_;

 private:
  base::MessageLoop message_loop_;
  notifier::FakePushClient* const fake_push_client_;

 protected:
  // A derrived test needs direct access to this.
  FakeInvalidationStateTracker fake_tracker_;

  // Tests need to access these directly.
  FakeInvalidationClient* fake_invalidation_client_;
  SyncInvalidationListener listener_;

 private:
  FakeDelegate fake_delegate_;
};

// Write a new state to the client.  It should propagate to the
// tracker.
TEST_F(SyncInvalidationListenerTest, WriteState) {
  WriteState(kNewState);

  EXPECT_EQ(kNewState, GetBootstrapData());
}

// Invalidation tests.

// Fire an invalidation without a payload.  It should be processed,
// the payload should remain empty, and the version should be updated.
TEST_F(SyncInvalidationListenerTest, InvalidateNoPayload) {
  const ObjectId& id = kBookmarksId_;

  FireInvalidate(id, kVersion1, NULL);

  ASSERT_EQ(1U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kVersion1, GetVersion(id));
  EXPECT_EQ("", GetPayload(id));
}

// Fire an invalidation with an empty payload.  It should be
// processed, the payload should remain empty, and the version should
// be updated.
TEST_F(SyncInvalidationListenerTest, InvalidateEmptyPayload) {
  const ObjectId& id = kBookmarksId_;

  FireInvalidate(id, kVersion1, "");

  ASSERT_EQ(1U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kVersion1, GetVersion(id));
  EXPECT_EQ("", GetPayload(id));
}

// Fire an invalidation with a payload.  It should be processed, and
// both the payload and the version should be updated.
TEST_F(SyncInvalidationListenerTest, InvalidateWithPayload) {
  const ObjectId& id = kPreferencesId_;

  FireInvalidate(id, kVersion1, kPayload1);

  ASSERT_EQ(1U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kVersion1, GetVersion(id));
  EXPECT_EQ(kPayload1, GetPayload(id));
}

// Fire ten invalidations in a row.  All should be received.
TEST_F(SyncInvalidationListenerTest, ManyInvalidations_NoDrop) {
  const int kRepeatCount = 10;
  const ObjectId& id = kPreferencesId_;
  int64 initial_version = kVersion1;
  for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) {
    FireInvalidate(id, i, kPayload1);
  }
  ASSERT_EQ(static_cast<size_t>(kRepeatCount), GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kPayload1, GetPayload(id));
  EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id));
}

// Fire an invalidation for an unregistered object ID with a payload.  It should
// still be processed, and both the payload and the version should be updated.
TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Simple) {
  const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered");
  const ObjectId& id = kUnregisteredId;
  ObjectIdSet ids;
  ids.insert(id);

  EXPECT_EQ(0U, GetInvalidationCount(id));

  FireInvalidate(id, kVersion1, kPayload1);

  ASSERT_EQ(0U, GetInvalidationCount(id));

  EnableNotifications();
  listener_.Ready(fake_invalidation_client_);
  listener_.UpdateRegisteredIds(ids);

  ASSERT_EQ(1U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kVersion1, GetVersion(id));
  EXPECT_EQ(kPayload1, GetPayload(id));
}

// Fire ten invalidations before an object registers.  Some invalidations will
// be dropped an replaced with an unknown version invalidation.
TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Drop) {
  const int kRepeatCount =
      UnackedInvalidationSet::kMaxBufferedInvalidations + 1;
  const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered");
  const ObjectId& id = kUnregisteredId;
  ObjectIdSet ids;
  ids.insert(id);

  EXPECT_EQ(0U, GetInvalidationCount(id));

  int64 initial_version = kVersion1;
  for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) {
    FireInvalidate(id, i, kPayload1);
  }

  EnableNotifications();
  listener_.Ready(fake_invalidation_client_);
  listener_.UpdateRegisteredIds(ids);

  ASSERT_EQ(UnackedInvalidationSet::kMaxBufferedInvalidations,
            GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id));
  EXPECT_EQ(kPayload1, GetPayload(id));
  EXPECT_TRUE(StartsWithUnknownVersion(id));
}

// Fire an invalidation, then fire another one with a lower version.  Both
// should be received.
TEST_F(SyncInvalidationListenerTest, InvalidateVersion) {
  const ObjectId& id = kPreferencesId_;

  FireInvalidate(id, kVersion2, kPayload2);

  ASSERT_EQ(1U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));
  EXPECT_EQ(kVersion2, GetVersion(id));
  EXPECT_EQ(kPayload2, GetPayload(id));

  FireInvalidate(id, kVersion1, kPayload1);

  ASSERT_EQ(2U, GetInvalidationCount(id));
  ASSERT_FALSE(IsUnknownVersion(id));

  EXPECT_EQ(kVersion1, GetVersion(id));
  EXPECT_EQ(kPayload1, GetPayload(id));
}

// Fire an invalidation with an unknown version.
TEST_F(SyncInvalidationListenerTest, InvalidateUnknownVersion) {
  const ObjectId& id = kBookmarksId_;

  FireInvalidateUnknownVersion(id);

  ASSERT_EQ(1U, GetInvalidationCount(id));
  EXPECT_TRUE(IsUnknownVersion(id));
}

// Fire an invalidation for all enabled IDs.
TEST_F(SyncInvalidationListenerTest, InvalidateAll) {
  FireInvalidateAll();

  for (ObjectIdSet::const_iterator it = registered_ids_.begin();
       it != registered_ids_.end(); ++it) {
    ASSERT_EQ(1U, GetInvalidationCount(*it));
    EXPECT_TRUE(IsUnknownVersion(*it));
  }
}

// Test a simple scenario for multiple IDs.
TEST_F(SyncInvalidationListenerTest, InvalidateMultipleIds) {
  FireInvalidate(kBookmarksId_, 3, NULL);

  ASSERT_EQ(1U, GetInvalidationCount(kBookmarksId_));
  ASSERT_FALSE(IsUnknownVersion(kBookmarksId_));
  EXPECT_EQ(3, GetVersion(kBookmarksId_));
  EXPECT_EQ("", GetPayload(kBookmarksId_));

  // kExtensionId is not registered, so the invalidation should not get through.
  FireInvalidate(kExtensionsId_, 2, NULL);
  ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_));
}

// Registration tests.

// With IDs already registered, enable notifications then ready the
// client.  The IDs should be registered only after the client is
// readied.
TEST_F(SyncInvalidationListenerTest, RegisterEnableReady) {
  EXPECT_TRUE(GetRegisteredIds().empty());

  EnableNotifications();

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// With IDs already registered, ready the client then enable
// notifications.  The IDs should be registered after the client is
// readied.
TEST_F(SyncInvalidationListenerTest, RegisterReadyEnable) {
  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());

  EnableNotifications();

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// Unregister the IDs, enable notifications, re-register the IDs, then
// ready the client.  The IDs should be registered only after the
// client is readied.
TEST_F(SyncInvalidationListenerTest, EnableRegisterReady) {
  listener_.UpdateRegisteredIds(ObjectIdSet());

  EXPECT_TRUE(GetRegisteredIds().empty());

  EnableNotifications();

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.UpdateRegisteredIds(registered_ids_);

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// Unregister the IDs, enable notifications, ready the client, then
// re-register the IDs.  The IDs should be registered only after the
// client is readied.
TEST_F(SyncInvalidationListenerTest, EnableReadyRegister) {
  listener_.UpdateRegisteredIds(ObjectIdSet());

  EXPECT_TRUE(GetRegisteredIds().empty());

  EnableNotifications();

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.UpdateRegisteredIds(registered_ids_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// Unregister the IDs, ready the client, enable notifications, then
// re-register the IDs.  The IDs should be registered only after the
// client is readied.
TEST_F(SyncInvalidationListenerTest, ReadyEnableRegister) {
  listener_.UpdateRegisteredIds(ObjectIdSet());

  EXPECT_TRUE(GetRegisteredIds().empty());

  EnableNotifications();

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.UpdateRegisteredIds(registered_ids_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// Unregister the IDs, ready the client, re-register the IDs, then
// enable notifications. The IDs should be registered only after the
// client is readied.
//
// This test is important: see http://crbug.com/139424.
TEST_F(SyncInvalidationListenerTest, ReadyRegisterEnable) {
  listener_.UpdateRegisteredIds(ObjectIdSet());

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.UpdateRegisteredIds(registered_ids_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());

  EnableNotifications();

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// With IDs already registered, ready the client, restart the client,
// then re-ready it.  The IDs should still be registered.
TEST_F(SyncInvalidationListenerTest, RegisterTypesPreserved) {
  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());

  RestartClient();

  EXPECT_TRUE(GetRegisteredIds().empty());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(registered_ids_, GetRegisteredIds());
}

// Make sure that state is correctly purged from the local invalidation state
// map cache when an ID is unregistered.
TEST_F(SyncInvalidationListenerTest, UnregisterCleansUpStateMapCache) {
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetSavedInvalidations().empty());
  FireInvalidate(id, 1, "hello");
  EXPECT_EQ(1U, GetSavedInvalidations().size());
  EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
  FireInvalidate(kPreferencesId_, 2, "world");
  EXPECT_EQ(2U, GetSavedInvalidations().size());

  EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
  EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), kPreferencesId_));

  ObjectIdSet ids;
  ids.insert(id);
  listener_.UpdateRegisteredIds(ids);
  EXPECT_EQ(1U, GetSavedInvalidations().size());
  EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id));
}

TEST_F(SyncInvalidationListenerTest, DuplicateInvaldiations_Simple) {
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  // Send a stream of invalidations, including two copies of the second.
  FireInvalidate(id, 1, "one");
  FireInvalidate(id, 2, "two");
  FireInvalidate(id, 3, "three");
  FireInvalidate(id, 2, "two");

  // Expect that the duplicate was discarded.
  SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
  EXPECT_EQ(3U, list.GetSize());
  SingleObjectInvalidationSet::const_iterator it = list.begin();
  EXPECT_EQ(1, it->version());
  it++;
  EXPECT_EQ(2, it->version());
  it++;
  EXPECT_EQ(3, it->version());
}

TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_NearBufferLimit) {
  const size_t kPairsToSend = UnackedInvalidationSet::kMaxBufferedInvalidations;
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  // We will have enough buffer space in the state tracker for all these
  // invalidations only if duplicates are ignored.
  for (size_t i = 0; i < kPairsToSend; ++i) {
    FireInvalidate(id, i, "payload");
    FireInvalidate(id, i, "payload");
  }

  // Expect that the state map ignored duplicates.
  SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
  EXPECT_EQ(kPairsToSend, list.GetSize());
  EXPECT_FALSE(list.begin()->is_unknown_version());

  // Expect that all invalidations (including duplicates) were emitted.
  EXPECT_EQ(kPairsToSend*2, GetInvalidationCount(id));

  // Acknowledge all invalidations to clear the internal state.
  AcknowledgeAll(id);
  EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty());
}

TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_UnknownVersion) {
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  FireInvalidateUnknownVersion(id);
  FireInvalidateUnknownVersion(id);

  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    EXPECT_EQ(1U, list.GetSize());
  }

  // Acknowledge the second.  There should be no effect on the stored list.
  ASSERT_EQ(2U, GetInvalidationCount(id));
  AcknowledgeNthInvalidation(id, 1);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    EXPECT_EQ(1U, list.GetSize());
  }

  // Acknowledge the first.  This should remove the invalidation from the list.
  ASSERT_EQ(2U, GetInvalidationCount(id));
  AcknowledgeNthInvalidation(id, 0);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    EXPECT_EQ(0U, list.GetSize());
  }
}

// Make sure that acknowledgements erase items from the local store.
TEST_F(SyncInvalidationListenerTest, AcknowledgementsCleanUpStateMapCache) {
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetSavedInvalidations().empty());
  FireInvalidate(id, 10, "hello");
  FireInvalidate(id, 20, "world");
  FireInvalidateUnknownVersion(id);

  // Expect that all three invalidations have been saved to permanent storage.
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(3U, list.GetSize());
    EXPECT_TRUE(list.begin()->is_unknown_version());
    EXPECT_EQ(20, list.back().version());
  }

  // Acknowledge the second sent invaldiation (version 20) and verify it was
  // removed from storage.
  AcknowledgeNthInvalidation(id, 1);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(2U, list.GetSize());
    EXPECT_TRUE(list.begin()->is_unknown_version());
    EXPECT_EQ(10, list.back().version());
  }

  // Acknowledge the last sent invalidation (unknown version) and verify it was
  // removed from storage.
  AcknowledgeNthInvalidation(id, 2);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(1U, list.GetSize());
    EXPECT_FALSE(list.begin()->is_unknown_version());
    EXPECT_EQ(10, list.back().version());
  }
}

// Make sure that drops erase items from the local store.
TEST_F(SyncInvalidationListenerTest, DropsCleanUpStateMapCache) {
  const ObjectId& id = kBookmarksId_;
  listener_.Ready(fake_invalidation_client_);

  EXPECT_TRUE(GetSavedInvalidations().empty());
  FireInvalidate(id, 10, "hello");
  FireInvalidate(id, 20, "world");
  FireInvalidateUnknownVersion(id);

  // Expect that all three invalidations have been saved to permanent storage.
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(3U, list.GetSize());
    EXPECT_TRUE(list.begin()->is_unknown_version());
    EXPECT_EQ(20, list.back().version());
  }

  // Drop the second sent invalidation (version 20) and verify it was removed
  // from storage.  Also verify we still have an unknown version invalidation.
  DropNthInvalidation(id, 1);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(2U, list.GetSize());
    EXPECT_TRUE(list.begin()->is_unknown_version());
    EXPECT_EQ(10, list.back().version());
  }

  // Drop the remaining invalidation.  Verify an unknown version is all that
  // remains.
  DropNthInvalidation(id, 0);
  {
    SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id);
    ASSERT_EQ(1U, list.GetSize());
    EXPECT_TRUE(list.begin()->is_unknown_version());
  }

  // Announce that the delegate has recovered from the drop.  Verify no
  // invalidations remain saved.
  RecoverFromDropEvent(id);
  EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty());

  RecoverFromDropEvent(id);
}

// Without readying the client, disable notifications, then enable
// them.  The listener should still think notifications are disabled.
TEST_F(SyncInvalidationListenerTest, EnableNotificationsNotReady) {
  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR,
            GetInvalidatorState());

  DisableNotifications(
      notifier::TRANSIENT_NOTIFICATION_ERROR);

  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());

  DisableNotifications(notifier::NOTIFICATION_CREDENTIALS_REJECTED);

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  EnableNotifications();

  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());
}

// Enable notifications then Ready the invalidation client.  The
// delegate should then be ready.
TEST_F(SyncInvalidationListenerTest, EnableNotificationsThenReady) {
  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());

  EnableNotifications();

  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
}

// Ready the invalidation client then enable notifications.  The
// delegate should then be ready.
TEST_F(SyncInvalidationListenerTest, ReadyThenEnableNotifications) {
  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState());

  EnableNotifications();

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
}

// Enable notifications and ready the client.  Then disable
// notifications with an auth error and re-enable notifications.  The
// delegate should go into an auth error mode and then back out.
TEST_F(SyncInvalidationListenerTest, PushClientAuthError) {
  EnableNotifications();
  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());

  DisableNotifications(
      notifier::NOTIFICATION_CREDENTIALS_REJECTED);

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  EnableNotifications();

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
}

// Enable notifications and ready the client.  Then simulate an auth
// error from the invalidation client.  Simulate some notification
// events, then re-ready the client.  The delegate should go into an
// auth error mode and come out of it only after the client is ready.
TEST_F(SyncInvalidationListenerTest, InvalidationClientAuthError) {
  EnableNotifications();
  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());

  listener_.InformError(
      fake_invalidation_client_,
      invalidation::ErrorInfo(
          invalidation::ErrorReason::AUTH_FAILURE,
          false /* is_transient */,
          "auth error",
          invalidation::ErrorContext()));

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR);

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR);

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  EnableNotifications();

  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState());

  listener_.Ready(fake_invalidation_client_);

  EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState());
}

// A variant of SyncInvalidationListenerTest that starts with some initial
// state.  We make not attempt to abstract away the contents of this state.  The
// tests that make use of this harness depend on its implementation details.
class SyncInvalidationListenerTest_WithInitialState
    : public SyncInvalidationListenerTest {
 public:
  virtual void SetUp() {
    UnackedInvalidationSet bm_state(kBookmarksId_);
    UnackedInvalidationSet ext_state(kExtensionsId_);

    Invalidation bm_unknown = Invalidation::InitUnknownVersion(kBookmarksId_);
    Invalidation bm_v100 = Invalidation::Init(kBookmarksId_, 100, "hundred");
    bm_state.Add(bm_unknown);
    bm_state.Add(bm_v100);

    Invalidation ext_v10 = Invalidation::Init(kExtensionsId_, 10, "ten");
    Invalidation ext_v20 = Invalidation::Init(kExtensionsId_, 20, "twenty");
    ext_state.Add(ext_v10);
    ext_state.Add(ext_v20);

    initial_state.insert(std::make_pair(kBookmarksId_, bm_state));
    initial_state.insert(std::make_pair(kExtensionsId_, ext_state));

    fake_tracker_.SetSavedInvalidations(initial_state);

    SyncInvalidationListenerTest::SetUp();
  }

  UnackedInvalidationsMap initial_state;
};

// Verify that saved invalidations are forwarded when handlers register.
TEST_F(SyncInvalidationListenerTest_WithInitialState,
       ReceiveSavedInvalidations) {
  EnableNotifications();
  listener_.Ready(fake_invalidation_client_);

  EXPECT_THAT(initial_state, test_util::Eq(GetSavedInvalidations()));

  ASSERT_EQ(2U, GetInvalidationCount(kBookmarksId_));
  EXPECT_EQ(100, GetVersion(kBookmarksId_));

  ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_));

  FireInvalidate(kExtensionsId_, 30, "thirty");

  ObjectIdSet ids = GetRegisteredIds();
  ids.insert(kExtensionsId_);
  listener_.UpdateRegisteredIds(ids);

  ASSERT_EQ(3U, GetInvalidationCount(kExtensionsId_));
  EXPECT_EQ(30, GetVersion(kExtensionsId_));
}

}  // namespace

}  // namespace syncer

/* [<][>][^][v][top][bottom][index][help] */