// Copyright 2013 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 "sync/notifier/unacked_invalidation_set.h"

#include "base/json/json_string_value_serializer.h"
#include "sync/notifier/object_id_invalidation_map.h"
#include "sync/notifier/single_object_invalidation_set.h"
#include "sync/notifier/unacked_invalidation_set_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

class UnackedInvalidationSetTest : public testing::Test {
 public:
  UnackedInvalidationSetTest()
      : kObjectId_(10, "ASDF"),
        unacked_invalidations_(kObjectId_) {}

  SingleObjectInvalidationSet GetStoredInvalidations() {
    ObjectIdInvalidationMap map;
    unacked_invalidations_.ExportInvalidations(WeakHandle<AckHandler>(), &map);
    ObjectIdSet ids = map.GetObjectIds();
    if (ids.find(kObjectId_) != ids.end()) {
      return map.ForObject(kObjectId_);
    } else {
      return SingleObjectInvalidationSet();
    }
  }

  const invalidation::ObjectId kObjectId_;
  UnackedInvalidationSet unacked_invalidations_;
};

namespace {

// Test storage and retrieval of zero invalidations.
TEST_F(UnackedInvalidationSetTest, Empty) {
  EXPECT_EQ(0U, GetStoredInvalidations().GetSize());
}

// Test storage and retrieval of a single invalidation.
TEST_F(UnackedInvalidationSetTest, OneInvalidation) {
  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  unacked_invalidations_.Add(inv1);

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(1U, set.GetSize());
  EXPECT_FALSE(set.StartsWithUnknownVersion());
}

// Test that calling Clear() returns us to the empty state.
TEST_F(UnackedInvalidationSetTest, Clear) {
  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  unacked_invalidations_.Add(inv1);
  unacked_invalidations_.Clear();

  EXPECT_EQ(0U, GetStoredInvalidations().GetSize());
}

// Test that repeated unknown version invalidations are squashed together.
TEST_F(UnackedInvalidationSetTest, UnknownVersions) {
  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  Invalidation inv2 = Invalidation::InitUnknownVersion(kObjectId_);
  Invalidation inv3 = Invalidation::InitUnknownVersion(kObjectId_);
  unacked_invalidations_.Add(inv1);
  unacked_invalidations_.Add(inv2);
  unacked_invalidations_.Add(inv3);

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(2U, set.GetSize());
  EXPECT_TRUE(set.StartsWithUnknownVersion());
}

// Tests that no truncation occurs while we're under the limit.
TEST_F(UnackedInvalidationSetTest, NoTruncation) {
  size_t kMax = UnackedInvalidationSet::kMaxBufferedInvalidations;

  for (size_t i = 0; i < kMax; ++i) {
    Invalidation inv = Invalidation::Init(kObjectId_, i, "payload");
    unacked_invalidations_.Add(inv);
  }

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(kMax, set.GetSize());
  EXPECT_FALSE(set.StartsWithUnknownVersion());
  EXPECT_EQ(0, set.begin()->version());
  EXPECT_EQ(kMax-1, static_cast<size_t>(set.rbegin()->version()));
}

// Test that truncation happens as we reach the limit.
TEST_F(UnackedInvalidationSetTest, Truncation) {
  size_t kMax = UnackedInvalidationSet::kMaxBufferedInvalidations;

  for (size_t i = 0; i < kMax + 1; ++i) {
    Invalidation inv = Invalidation::Init(kObjectId_, i, "payload");
    unacked_invalidations_.Add(inv);
  }

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(kMax, set.GetSize());
  EXPECT_TRUE(set.StartsWithUnknownVersion());
  EXPECT_TRUE(set.begin()->is_unknown_version());
  EXPECT_EQ(kMax, static_cast<size_t>(set.rbegin()->version()));
}

// Test that we don't truncate while a handler is registered.
TEST_F(UnackedInvalidationSetTest, RegistrationAndTruncation) {
  unacked_invalidations_.SetHandlerIsRegistered();

  size_t kMax = UnackedInvalidationSet::kMaxBufferedInvalidations;

  for (size_t i = 0; i < kMax + 1; ++i) {
    Invalidation inv = Invalidation::Init(kObjectId_, i, "payload");
    unacked_invalidations_.Add(inv);
  }

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(kMax+1, set.GetSize());
  EXPECT_FALSE(set.StartsWithUnknownVersion());
  EXPECT_EQ(0, set.begin()->version());
  EXPECT_EQ(kMax, static_cast<size_t>(set.rbegin()->version()));

  // Unregistering should re-enable truncation.
  unacked_invalidations_.SetHandlerIsUnregistered();
  SingleObjectInvalidationSet set2 = GetStoredInvalidations();
  ASSERT_EQ(kMax, set2.GetSize());
  EXPECT_TRUE(set2.StartsWithUnknownVersion());
  EXPECT_TRUE(set2.begin()->is_unknown_version());
  EXPECT_EQ(kMax, static_cast<size_t>(set2.rbegin()->version()));
}

// Test acknowledgement.
TEST_F(UnackedInvalidationSetTest, Acknowledge) {
  // inv2 is included in this test just to make sure invalidations that
  // are supposed to be unaffected by this operation will be unaffected.

  // We don't expect to be receiving acks or drops unless this flag is set.
  // Not that it makes much of a difference in behavior.
  unacked_invalidations_.SetHandlerIsRegistered();

  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  Invalidation inv2 = Invalidation::InitUnknownVersion(kObjectId_);
  AckHandle inv1_handle = inv1.ack_handle();

  unacked_invalidations_.Add(inv1);
  unacked_invalidations_.Add(inv2);

  unacked_invalidations_.Acknowledge(inv1_handle);

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  EXPECT_EQ(1U, set.GetSize());
  EXPECT_TRUE(set.StartsWithUnknownVersion());
}

// Test drops.
TEST_F(UnackedInvalidationSetTest, Drop) {
  // inv2 is included in this test just to make sure invalidations that
  // are supposed to be unaffected by this operation will be unaffected.

  // We don't expect to be receiving acks or drops unless this flag is set.
  // Not that it makes much of a difference in behavior.
  unacked_invalidations_.SetHandlerIsRegistered();

  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  Invalidation inv2 = Invalidation::Init(kObjectId_, 15, "payload");
  AckHandle inv1_handle = inv1.ack_handle();

  unacked_invalidations_.Add(inv1);
  unacked_invalidations_.Add(inv2);

  unacked_invalidations_.Drop(inv1_handle);

  SingleObjectInvalidationSet set = GetStoredInvalidations();
  ASSERT_EQ(2U, set.GetSize());
  EXPECT_TRUE(set.StartsWithUnknownVersion());
  EXPECT_EQ(15, set.rbegin()->version());
}

class UnackedInvalidationSetSerializationTest
    : public UnackedInvalidationSetTest {
 public:
  UnackedInvalidationSet SerializeDeserialize() {
    scoped_ptr<base::DictionaryValue> value = unacked_invalidations_.ToValue();
    UnackedInvalidationSet deserialized(kObjectId_);
    deserialized.ResetFromValue(*value.get());
    return deserialized;
  }
};

TEST_F(UnackedInvalidationSetSerializationTest, Empty) {
  UnackedInvalidationSet deserialized = SerializeDeserialize();
  EXPECT_THAT(unacked_invalidations_, test_util::Eq(deserialized));
}

TEST_F(UnackedInvalidationSetSerializationTest, OneInvalidation) {
  Invalidation inv = Invalidation::Init(kObjectId_, 10, "payload");
  unacked_invalidations_.Add(inv);

  UnackedInvalidationSet deserialized = SerializeDeserialize();
  EXPECT_THAT(unacked_invalidations_, test_util::Eq(deserialized));
}

TEST_F(UnackedInvalidationSetSerializationTest, WithUnknownVersion) {
  Invalidation inv1 = Invalidation::Init(kObjectId_, 10, "payload");
  Invalidation inv2 = Invalidation::InitUnknownVersion(kObjectId_);
  Invalidation inv3 = Invalidation::InitUnknownVersion(kObjectId_);
  unacked_invalidations_.Add(inv1);
  unacked_invalidations_.Add(inv2);
  unacked_invalidations_.Add(inv3);

  UnackedInvalidationSet deserialized = SerializeDeserialize();
  EXPECT_THAT(unacked_invalidations_, test_util::Eq(deserialized));
}

}  // namespace

}  // namespace syncer